mirror of https://github.com/rwf2/Rocket.git
Graduate 'serve' into core as 'fs', 'FileServer'.
This completes the graduation of stable 'contrib' features to 'core'. Closes #1107.
This commit is contained in:
parent
a78814f1c5
commit
b1d05d20ac
|
@ -20,10 +20,9 @@ databases = [
|
|||
]
|
||||
|
||||
# User-facing features.
|
||||
default = ["serve"]
|
||||
default = []
|
||||
tera_templates = ["tera", "templates"]
|
||||
handlebars_templates = ["handlebars", "templates"]
|
||||
serve = []
|
||||
compression = ["brotli_compression", "gzip_compression"]
|
||||
brotli_compression = ["brotli"]
|
||||
gzip_compression = ["flate2"]
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
//! common modules exposed by default. The present feature list is below, with
|
||||
//! an asterisk next to the features that are enabled by default:
|
||||
//!
|
||||
//! * [serve*](serve) - Static File Serving
|
||||
//! * [handlebars_templates](templates) - Handlebars Templating
|
||||
//! * [tera_templates](templates) - Tera Templating
|
||||
//! * [${database}_pool](databases) - Database Configuration and Pooling
|
||||
|
@ -39,7 +38,6 @@
|
|||
|
||||
#[allow(unused_imports)] #[macro_use] extern crate rocket;
|
||||
|
||||
#[cfg(feature="serve")] pub mod serve;
|
||||
#[cfg(feature="templates")] pub mod templates;
|
||||
#[cfg(feature="databases")] pub mod databases;
|
||||
// TODO.async: Migrate compression, reenable this, tests, and add to docs.
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
#[cfg(feature = "serve")]
|
||||
mod static_tests {
|
||||
use std::{io::Read, fs::File};
|
||||
use std::path::Path;
|
||||
|
||||
use rocket::{Rocket, Route, Build};
|
||||
use rocket::http::Status;
|
||||
use rocket::local::blocking::Client;
|
||||
|
||||
use rocket_contrib::serve::{StaticFiles, Options, crate_relative};
|
||||
|
||||
fn static_root() -> &'static Path {
|
||||
Path::new(crate_relative!("/tests/static"))
|
||||
}
|
||||
|
||||
fn rocket() -> Rocket<Build> {
|
||||
let root = static_root();
|
||||
rocket::build()
|
||||
.mount("/default", StaticFiles::from(&root))
|
||||
.mount("/no_index", StaticFiles::new(&root, Options::None))
|
||||
.mount("/dots", StaticFiles::new(&root, Options::DotFiles))
|
||||
.mount("/index", StaticFiles::new(&root, Options::Index))
|
||||
.mount("/both", StaticFiles::new(&root, Options::DotFiles | Options::Index))
|
||||
.mount("/redir", StaticFiles::new(&root, Options::NormalizeDirs))
|
||||
.mount("/redir_index", StaticFiles::new(&root, Options::NormalizeDirs | Options::Index))
|
||||
}
|
||||
|
||||
static REGULAR_FILES: &[&str] = &[
|
||||
"index.html",
|
||||
"inner/goodbye",
|
||||
"inner/index.html",
|
||||
"other/hello.txt",
|
||||
];
|
||||
|
||||
static HIDDEN_FILES: &[&str] = &[
|
||||
".hidden",
|
||||
"inner/.hideme",
|
||||
];
|
||||
|
||||
static INDEXED_DIRECTORIES: &[&str] = &[
|
||||
"",
|
||||
"inner/",
|
||||
];
|
||||
|
||||
fn assert_file(client: &Client, prefix: &str, path: &str, exists: bool) {
|
||||
let full_path = format!("/{}/{}", prefix, path);
|
||||
let response = client.get(full_path).dispatch();
|
||||
if exists {
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let mut path = static_root().join(path);
|
||||
if path.is_dir() {
|
||||
path = path.join("index.html");
|
||||
}
|
||||
|
||||
let mut file = File::open(path).expect("open file");
|
||||
let mut expected_contents = String::new();
|
||||
file.read_to_string(&mut expected_contents).expect("read file");
|
||||
assert_eq!(response.into_string(), Some(expected_contents));
|
||||
} else {
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_all(client: &Client, prefix: &str, paths: &[&str], exist: bool) {
|
||||
for path in paths.iter() {
|
||||
assert_file(client, prefix, path, exist);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_no_index() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "no_index", REGULAR_FILES, true);
|
||||
assert_all(&client, "no_index", HIDDEN_FILES, false);
|
||||
assert_all(&client, "no_index", INDEXED_DIRECTORIES, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_hidden() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "dots", REGULAR_FILES, true);
|
||||
assert_all(&client, "dots", HIDDEN_FILES, true);
|
||||
assert_all(&client, "dots", INDEXED_DIRECTORIES, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_index() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "index", REGULAR_FILES, true);
|
||||
assert_all(&client, "index", HIDDEN_FILES, false);
|
||||
assert_all(&client, "index", INDEXED_DIRECTORIES, true);
|
||||
|
||||
assert_all(&client, "default", REGULAR_FILES, true);
|
||||
assert_all(&client, "default", HIDDEN_FILES, false);
|
||||
assert_all(&client, "default", INDEXED_DIRECTORIES, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_all() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "both", REGULAR_FILES, true);
|
||||
assert_all(&client, "both", HIDDEN_FILES, true);
|
||||
assert_all(&client, "both", INDEXED_DIRECTORIES, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranking() {
|
||||
let root = static_root();
|
||||
for rank in -128..128 {
|
||||
let a = StaticFiles::new(&root, Options::None).rank(rank);
|
||||
let b = StaticFiles::from(&root).rank(rank);
|
||||
|
||||
for handler in vec![a, b] {
|
||||
let routes: Vec<Route> = handler.into();
|
||||
assert!(routes.iter().all(|route| route.rank == rank), "{}", rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forwarding() {
|
||||
use rocket::{get, routes};
|
||||
|
||||
#[get("/<value>", rank = 20)]
|
||||
fn catch_one(value: String) -> String { value }
|
||||
|
||||
#[get("/<a>/<b>", rank = 20)]
|
||||
fn catch_two(a: &str, b: &str) -> String { format!("{}/{}", a, b) }
|
||||
|
||||
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
|
||||
let client = Client::debug(rocket).expect("valid rocket");
|
||||
|
||||
let response = client.get("/default/ireallydontexist").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.into_string().unwrap(), "ireallydontexist");
|
||||
|
||||
let response = client.get("/default/idont/exist").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.into_string().unwrap(), "idont/exist");
|
||||
|
||||
assert_all(&client, "both", REGULAR_FILES, true);
|
||||
assert_all(&client, "both", HIDDEN_FILES, true);
|
||||
assert_all(&client, "both", INDEXED_DIRECTORIES, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_redirection() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
|
||||
// Redirection only happens if enabled, and doesn't affect index behaviour.
|
||||
let response = client.get("/no_index/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client.get("/index/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let response = client.get("/redir/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir/inner/"));
|
||||
|
||||
let response = client.get("/redir/inner?foo=bar").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir/inner/?foo=bar"));
|
||||
|
||||
let response = client.get("/redir_index/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir_index/inner/"));
|
||||
|
||||
// Paths with trailing slash are unaffected.
|
||||
let response = client.get("/redir/inner/").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client.get("/redir_index/inner/").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
// Root of route is also redirected.
|
||||
let response = client.get("/no_index").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client.get("/index").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let response = client.get("/redir").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir/"));
|
||||
|
||||
let response = client.get("/redir_index").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir_index/"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
//! File serving, file accepting, and file system types.
|
||||
|
||||
mod server;
|
||||
|
||||
pub use server::*;
|
||||
pub use server::relative;
|
|
@ -1,64 +1,220 @@
|
|||
//! Custom handler and options for static file serving.
|
||||
//!
|
||||
//! See the [`StaticFiles`](crate::serve::StaticFiles) type for further details.
|
||||
//!
|
||||
//! # Enabling
|
||||
//!
|
||||
//! This module is only available when the `serve` feature is enabled. Enable it
|
||||
//! in `Cargo.toml` as follows:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies.rocket_contrib]
|
||||
//! version = "0.5.0-dev"
|
||||
//! default-features = false
|
||||
//! features = ["serve"]
|
||||
//! ```
|
||||
|
||||
use std::path::{PathBuf, Path};
|
||||
|
||||
use rocket::{Request, Data};
|
||||
use rocket::http::{Method, uri::Segments, ext::IntoOwned};
|
||||
use rocket::response::{NamedFile, Redirect};
|
||||
use rocket::route::{Route, Handler, Outcome};
|
||||
use crate::{Request, Data};
|
||||
use crate::http::{Method, uri::Segments, ext::IntoOwned};
|
||||
use crate::response::{NamedFile, Redirect};
|
||||
use crate::route::{Route, Handler, Outcome};
|
||||
|
||||
/// Generates a crate-relative version of `$path`.
|
||||
/// Custom handler for serving static files.
|
||||
///
|
||||
/// This macro is primarily intended for use with [`StaticFiles`] to serve files
|
||||
/// from a path relative to the crate root. The macro accepts one parameter,
|
||||
/// `$path`, an absolute or, preferably, relative path. It returns a path (an
|
||||
/// `&'static str`) prefixed with the path to the crate root. Use `Path::new()`
|
||||
/// to retrieve an `&'static Path`.
|
||||
/// This handler makes it simple to serve static files from a directory on the
|
||||
/// local file system. To use it, construct a `FileServer` using either
|
||||
/// [`FileServer::from()`] or [`FileServer::new()`] then simply `mount` the
|
||||
/// handler at a desired path. When mounted, the handler will generate route(s)
|
||||
/// that serve the desired static files. If a requested file is not found, the
|
||||
/// routes _forward_ the incoming request. The default rank of the generated
|
||||
/// routes is `10`. To customize route ranking, use the [`FileServer::rank()`]
|
||||
/// method.
|
||||
///
|
||||
/// See the [relative paths `StaticFiles`
|
||||
/// documentation](`StaticFiles`#relative-paths) for an example.
|
||||
/// # Options
|
||||
///
|
||||
/// The handler's functionality can be customized by passing an [`Options`] to
|
||||
/// [`FileServer::new()`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::path::Path;
|
||||
/// use rocket_contrib::serve::crate_relative;
|
||||
/// To serve files from the `/static` directory on the local file system at the
|
||||
/// `/public` path, allowing `index.html` files to be used to respond to
|
||||
/// requests for a directory (the default), you might write the following:
|
||||
///
|
||||
/// let manual = Path::new(env!("CARGO_MANIFEST_DIR")).join("static");
|
||||
/// let automatic_1 = Path::new(crate_relative!("static"));
|
||||
/// let automatic_2 = Path::new(crate_relative!("/static"));
|
||||
/// assert_eq!(manual, automatic_1);
|
||||
/// assert_eq!(automatic_1, automatic_2);
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::fs::FileServer;
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/public", FileServer::from("/static"))
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! crate_relative {
|
||||
($path:expr) => {
|
||||
if cfg!(windows) {
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "\\", $path)
|
||||
} else {
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "/", $path)
|
||||
}
|
||||
};
|
||||
///
|
||||
/// With this, requests for files at `/public/<path..>` will be handled by
|
||||
/// returning the contents of `/static/<path..>`. Requests for _directories_ at
|
||||
/// `/public/<directory>` will be handled by returning the contents of
|
||||
/// `/static/<directory>/index.html`.
|
||||
///
|
||||
/// ## Relative Paths
|
||||
///
|
||||
/// In the example above, `/static` is an absolute path. If your static files
|
||||
/// are stored relative to your crate and your project is managed by Rocket, use
|
||||
/// the [`relative!`] macro to obtain a path that is relative to your
|
||||
/// crate's root. For example, to serve files in the `static` subdirectory of
|
||||
/// your crate at `/`, you might write:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::fs::{FileServer, relative};
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/", FileServer::from(relative!("static")))
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileServer {
|
||||
root: PathBuf,
|
||||
options: Options,
|
||||
rank: isize,
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate_relative;
|
||||
impl FileServer {
|
||||
/// The default rank use by `FileServer` routes.
|
||||
const DEFAULT_RANK: isize = 10;
|
||||
|
||||
/// A bitset representing configurable options for the [`StaticFiles`] handler.
|
||||
/// Constructs a new `FileServer` that serves files from the file system
|
||||
/// `path`. By default, [`Options::Index`] is set, and the generated routes
|
||||
/// have a rank of `10`. To serve static files with other options, use
|
||||
/// [`FileServer::new()`]. To choose a different rank for generated routes,
|
||||
/// use [`FileServer::rank()`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `path` does not exist or is not a directory.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Serve the static files in the `/www/public` local directory on path
|
||||
/// `/static`.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::fs::FileServer;
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/static", FileServer::from("/www/public"))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Exactly as before, but set the rank for generated routes to `30`.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::fs::FileServer;
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/static", FileServer::from("/www/public").rank(30))
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from<P: AsRef<Path>>(path: P) -> Self {
|
||||
FileServer::new(path, Options::default())
|
||||
}
|
||||
|
||||
/// Constructs a new `FileServer` that serves files from the file system
|
||||
/// `path` with `options` enabled. By default, the handler's routes have a
|
||||
/// rank of `10`. To choose a different rank, use [`FileServer::rank()`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `path` does not exist or is not a directory.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Serve the static files in the `/www/public` local directory on path
|
||||
/// `/static` without serving index files or dot files. Additionally, serve
|
||||
/// the same files on `/pub` with a route rank of -1 while also serving
|
||||
/// index files and dot files.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::fs::{FileServer, Options};
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// let options = Options::Index | Options::DotFiles;
|
||||
/// rocket::build()
|
||||
/// .mount("/static", FileServer::from("/www/public"))
|
||||
/// .mount("/pub", FileServer::new("/www/public", options).rank(-1))
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new<P: AsRef<Path>>(path: P, options: Options) -> Self {
|
||||
use crate::yansi::Paint;
|
||||
|
||||
let path = path.as_ref();
|
||||
if !path.is_dir() {
|
||||
error!("`FileServer` supplied with invalid path");
|
||||
info_!("'{}' is not a directory", Paint::white(path.display()));
|
||||
panic!("refusing to continue due to invalid static files path");
|
||||
}
|
||||
|
||||
FileServer { root: path.into(), options, rank: Self::DEFAULT_RANK }
|
||||
}
|
||||
|
||||
/// Sets the rank for generated routes to `rank`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rocket::fs::{FileServer, Options};
|
||||
///
|
||||
/// // A `FileServer` created with `from()` with routes of rank `3`.
|
||||
/// FileServer::from("/public").rank(3);
|
||||
///
|
||||
/// // A `FileServer` created with `new()` with routes of rank `-15`.
|
||||
/// FileServer::new("/public", Options::Index).rank(-15);
|
||||
/// ```
|
||||
pub fn rank(mut self, rank: isize) -> Self {
|
||||
self.rank = rank;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<Route>> for FileServer {
|
||||
fn into(self) -> Vec<Route> {
|
||||
let source = figment::Source::File(self.root.clone());
|
||||
let mut route = Route::ranked(self.rank, Method::Get, "/<path..>", self);
|
||||
route.name = Some(format!("FileServer: {}/", source).into());
|
||||
vec![route]
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::async_trait]
|
||||
impl Handler for FileServer {
|
||||
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
|
||||
use crate::http::uri::fmt::Path;
|
||||
|
||||
// Get the segments as a `PathBuf`, allowing dotfiles requested.
|
||||
let options = self.options;
|
||||
let allow_dotfiles = options.contains(Options::DotFiles);
|
||||
let path = req.segments::<Segments<'_, Path>>(0..).ok()
|
||||
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
|
||||
.map(|path| self.root.join(path));
|
||||
|
||||
match path {
|
||||
Some(p) if p.is_dir() => {
|
||||
// Normalize '/a/b/foo' to '/a/b/foo/'.
|
||||
if options.contains(Options::NormalizeDirs) && !req.uri().path().ends_with('/') {
|
||||
let normal = req.uri().map_path(|p| format!("{}/", p))
|
||||
.expect("adding a trailing slash to a known good path => valid path")
|
||||
.into_owned();
|
||||
|
||||
return Outcome::from_or_forward(req, data, Redirect::permanent(normal));
|
||||
}
|
||||
|
||||
if !options.contains(Options::Index) {
|
||||
return Outcome::forward(data);
|
||||
}
|
||||
|
||||
let index = NamedFile::open(p.join("index.html")).await.ok();
|
||||
Outcome::from_or_forward(req, data, index)
|
||||
},
|
||||
Some(p) => Outcome::from_or_forward(req, data, NamedFile::open(p).await.ok()),
|
||||
None => Outcome::forward(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A bitset representing configurable options for [`FileServer`].
|
||||
///
|
||||
/// The valid options are:
|
||||
///
|
||||
|
@ -85,7 +241,7 @@ impl Options {
|
|||
/// Respond to requests for a directory with the `index.html` file in that
|
||||
/// directory, if it exists.
|
||||
///
|
||||
/// When enabled, [`StaticFiles`] will respond to requests for a directory
|
||||
/// When enabled, [`FileServer`] will respond to requests for a directory
|
||||
/// `/foo` or `/foo/` with the file at `${root}/foo/index.html` if it
|
||||
/// exists. When disabled, requests to directories will always forward.
|
||||
///
|
||||
|
@ -94,7 +250,7 @@ impl Options {
|
|||
|
||||
/// Allow requests to dotfiles.
|
||||
///
|
||||
/// When enabled, [`StaticFiles`] will respond to requests for files or
|
||||
/// When enabled, [`FileServer`] will respond to requests for files or
|
||||
/// directories beginning with `.`. When disabled, any dotfiles will be
|
||||
/// treated as missing.
|
||||
///
|
||||
|
@ -104,7 +260,7 @@ impl Options {
|
|||
/// Normalizes directory requests by redirecting requests to directory paths
|
||||
/// without a trailing slash to ones with a trailing slash.
|
||||
///
|
||||
/// When enabled, the [`StaticFiles`] handler will respond to requests for a
|
||||
/// When enabled, the [`FileServer`] handler will respond to requests for a
|
||||
/// directory without a trailing `/` with a permanent redirect (308) to the
|
||||
/// same path with a trailing `/`. This ensures relative URLs within any
|
||||
/// document served from that directory will be interpreted relative to that
|
||||
|
@ -123,7 +279,7 @@ impl Options {
|
|||
/// └── index.html
|
||||
/// ```
|
||||
///
|
||||
/// ...with `StaticFiles::from("static")`, both requests to `/foo` and
|
||||
/// ...with `FileServer::from("static")`, both requests to `/foo` and
|
||||
/// `/foo/` will serve `static/foo/index.html`. If `index.html` references
|
||||
/// `cat.jpeg` as a relative URL, the browser will request `/cat.jpeg`
|
||||
/// (`static/cat.jpeg`) when the request for `/foo` was handled and
|
||||
|
@ -139,7 +295,7 @@ impl Options {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::serve::Options;
|
||||
/// use rocket::fs::Options;
|
||||
///
|
||||
/// let index_request = Options::Index | Options::DotFiles;
|
||||
/// assert!(index_request.contains(Options::Index));
|
||||
|
@ -175,217 +331,52 @@ impl std::ops::BitOr for Options {
|
|||
}
|
||||
}
|
||||
|
||||
/// Custom handler for serving static files.
|
||||
///
|
||||
/// This handler makes it simple to serve static files from a directory on the
|
||||
/// local file system. To use it, construct a `StaticFiles` using either
|
||||
/// [`StaticFiles::from()`] or [`StaticFiles::new()`] then simply `mount` the
|
||||
/// handler at a desired path. When mounted, the handler will generate route(s)
|
||||
/// that serve the desired static files. If a requested file is not found, the
|
||||
/// routes _forward_ the incoming request. The default rank of the generated
|
||||
/// routes is `10`. To customize route ranking, use the [`StaticFiles::rank()`]
|
||||
/// method.
|
||||
///
|
||||
/// # Options
|
||||
///
|
||||
/// The handler's functionality can be customized by passing an [`Options`] to
|
||||
/// [`StaticFiles::new()`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// To serve files from the `/static` directory on the local file system at the
|
||||
/// `/public` path, allowing `index.html` files to be used to respond to
|
||||
/// requests for a directory (the default), you might write the following:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::serve::StaticFiles;
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/public", StaticFiles::from("/static"))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// With this, requests for files at `/public/<path..>` will be handled by
|
||||
/// returning the contents of `/static/<path..>`. Requests for _directories_ at
|
||||
/// `/public/<directory>` will be handled by returning the contents of
|
||||
/// `/static/<directory>/index.html`.
|
||||
///
|
||||
/// ## Relative Paths
|
||||
///
|
||||
/// In the example above, `/static` is an absolute path. If your static files
|
||||
/// are stored relative to your crate and your project is managed by Rocket, use
|
||||
/// the [`crate_relative!`] macro to obtain a path that is relative to your
|
||||
/// crate's root. For example, to serve files in the `static` subdirectory of
|
||||
/// your crate at `/`, you might write:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::serve::{StaticFiles, crate_relative};
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/", StaticFiles::from(crate_relative!("static")))
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct StaticFiles {
|
||||
root: PathBuf,
|
||||
options: Options,
|
||||
rank: isize,
|
||||
}
|
||||
|
||||
impl StaticFiles {
|
||||
/// The default rank use by `StaticFiles` routes.
|
||||
const DEFAULT_RANK: isize = 10;
|
||||
|
||||
/// Constructs a new `StaticFiles` that serves files from the file system
|
||||
/// `path`. By default, [`Options::Index`] is set, and the generated routes
|
||||
/// have a rank of `10`. To serve static files with other options, use
|
||||
/// [`StaticFiles::new()`]. To choose a different rank for generated routes,
|
||||
/// use [`StaticFiles::rank()`].
|
||||
crate::export! {
|
||||
/// Generates a crate-relative version of a path.
|
||||
///
|
||||
/// # Panics
|
||||
/// This macro is primarily intended for use with [`FileServer`] to serve
|
||||
/// files from a path relative to the crate root.
|
||||
///
|
||||
/// Panics if `path` does not exist or is not a directory.
|
||||
/// The macro accepts one parameter, `$path`, an absolute or (preferably)
|
||||
/// relative path. It returns a path as an `&'static str` prefixed with the
|
||||
/// path to the crate root. Use `Path::new(relative!($path))` to retrieve an
|
||||
/// `&'static Path`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Serve the static files in the `/www/public` local directory on path
|
||||
/// `/static`.
|
||||
/// Serve files from the crate-relative `static/` directory:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::serve::StaticFiles;
|
||||
/// use rocket::fs::{FileServer, relative};
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/static", StaticFiles::from("/www/public"))
|
||||
/// rocket::build().mount("/", FileServer::from(relative!("static")))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Exactly as before, but set the rank for generated routes to `30`.
|
||||
/// Path equivalences:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::serve::StaticFiles;
|
||||
/// ```rust
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/static", StaticFiles::from("/www/public").rank(30))
|
||||
/// }
|
||||
/// use rocket::fs::relative;
|
||||
///
|
||||
/// let manual = Path::new(env!("CARGO_MANIFEST_DIR")).join("static");
|
||||
/// let automatic_1 = Path::new(relative!("static"));
|
||||
/// let automatic_2 = Path::new(relative!("/static"));
|
||||
/// assert_eq!(manual, automatic_1);
|
||||
/// assert_eq!(automatic_1, automatic_2);
|
||||
/// ```
|
||||
pub fn from<P: AsRef<Path>>(path: P) -> Self {
|
||||
StaticFiles::new(path, Options::default())
|
||||
}
|
||||
|
||||
/// Constructs a new `StaticFiles` that serves files from the file system
|
||||
/// `path` with `options` enabled. By default, the handler's routes have a
|
||||
/// rank of `10`. To choose a different rank, use [`StaticFiles::rank()`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `path` does not exist or is not a directory.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Serve the static files in the `/www/public` local directory on path
|
||||
/// `/static` without serving index files or dot files. Additionally, serve
|
||||
/// the same files on `/pub` with a route rank of -1 while also serving
|
||||
/// index files and dot files.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::serve::{StaticFiles, Options};
|
||||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// let options = Options::Index | Options::DotFiles;
|
||||
/// rocket::build()
|
||||
/// .mount("/static", StaticFiles::from("/www/public"))
|
||||
/// .mount("/pub", StaticFiles::new("/www/public", options).rank(-1))
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new<P: AsRef<Path>>(path: P, options: Options) -> Self {
|
||||
use rocket::yansi::Paint;
|
||||
|
||||
let path = path.as_ref();
|
||||
if !path.is_dir() {
|
||||
error!("`StaticFiles` supplied with invalid path");
|
||||
info_!("'{}' is not a directory", Paint::white(path.display()));
|
||||
panic!("refusing to continue due to invalid static files path");
|
||||
}
|
||||
|
||||
StaticFiles { root: path.into(), options, rank: Self::DEFAULT_RANK }
|
||||
}
|
||||
|
||||
/// Sets the rank for generated routes to `rank`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::serve::{StaticFiles, Options};
|
||||
///
|
||||
/// // A `StaticFiles` created with `from()` with routes of rank `3`.
|
||||
/// StaticFiles::from("/public").rank(3);
|
||||
///
|
||||
/// // A `StaticFiles` created with `new()` with routes of rank `-15`.
|
||||
/// StaticFiles::new("/public", Options::Index).rank(-15);
|
||||
/// ```
|
||||
pub fn rank(mut self, rank: isize) -> Self {
|
||||
self.rank = rank;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<Route>> for StaticFiles {
|
||||
fn into(self) -> Vec<Route> {
|
||||
let source = rocket::figment::Source::File(self.root.clone());
|
||||
let mut route = Route::ranked(self.rank, Method::Get, "/<path..>", self);
|
||||
route.name = Some(format!("StaticFiles: {}/", source).into());
|
||||
vec![route]
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Handler for StaticFiles {
|
||||
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
|
||||
use rocket::http::uri::fmt::Path;
|
||||
|
||||
// Get the segments as a `PathBuf`, allowing dotfiles requested.
|
||||
let options = self.options;
|
||||
let allow_dotfiles = options.contains(Options::DotFiles);
|
||||
let path = req.segments::<Segments<'_, Path>>(0..).ok()
|
||||
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
|
||||
.map(|path| self.root.join(path));
|
||||
|
||||
match path {
|
||||
Some(p) if p.is_dir() => {
|
||||
// Normalize '/a/b/foo' to '/a/b/foo/'.
|
||||
if options.contains(Options::NormalizeDirs) && !req.uri().path().ends_with('/') {
|
||||
let normal = req.uri().map_path(|p| format!("{}/", p))
|
||||
.expect("adding a trailing slash to a known good path => valid path")
|
||||
.into_owned();
|
||||
|
||||
return Outcome::from_or_forward(req, data, Redirect::permanent(normal));
|
||||
}
|
||||
|
||||
if !options.contains(Options::Index) {
|
||||
return Outcome::forward(data);
|
||||
}
|
||||
|
||||
let index = NamedFile::open(p.join("index.html")).await.ok();
|
||||
Outcome::from_or_forward(req, data, index)
|
||||
},
|
||||
Some(p) => Outcome::from_or_forward(req, data, NamedFile::open(p).await.ok()),
|
||||
None => Outcome::forward(data),
|
||||
///
|
||||
macro_rules! relative {
|
||||
($path:expr) => {
|
||||
if cfg!(windows) {
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "\\", $path)
|
||||
} else {
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "/", $path)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -120,6 +120,7 @@ pub mod catcher;
|
|||
pub mod route;
|
||||
pub mod serde;
|
||||
pub mod shield;
|
||||
pub mod fs;
|
||||
|
||||
// Reexport of HTTP everything.
|
||||
pub mod http {
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
use std::{io::Read, fs::File};
|
||||
use std::path::Path;
|
||||
|
||||
use rocket::{Rocket, Route, Build};
|
||||
use rocket::http::Status;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::fs::{FileServer, Options, relative};
|
||||
|
||||
fn static_root() -> &'static Path {
|
||||
Path::new(relative!("/tests/static"))
|
||||
}
|
||||
|
||||
fn rocket() -> Rocket<Build> {
|
||||
let root = static_root();
|
||||
rocket::build()
|
||||
.mount("/default", FileServer::from(&root))
|
||||
.mount("/no_index", FileServer::new(&root, Options::None))
|
||||
.mount("/dots", FileServer::new(&root, Options::DotFiles))
|
||||
.mount("/index", FileServer::new(&root, Options::Index))
|
||||
.mount("/both", FileServer::new(&root, Options::DotFiles | Options::Index))
|
||||
.mount("/redir", FileServer::new(&root, Options::NormalizeDirs))
|
||||
.mount("/redir_index", FileServer::new(&root, Options::NormalizeDirs | Options::Index))
|
||||
}
|
||||
|
||||
static REGULAR_FILES: &[&str] = &[
|
||||
"index.html",
|
||||
"inner/goodbye",
|
||||
"inner/index.html",
|
||||
"other/hello.txt",
|
||||
];
|
||||
|
||||
static HIDDEN_FILES: &[&str] = &[
|
||||
".hidden",
|
||||
"inner/.hideme",
|
||||
];
|
||||
|
||||
static INDEXED_DIRECTORIES: &[&str] = &[
|
||||
"",
|
||||
"inner/",
|
||||
];
|
||||
|
||||
fn assert_file(client: &Client, prefix: &str, path: &str, exists: bool) {
|
||||
let full_path = format!("/{}/{}", prefix, path);
|
||||
let response = client.get(full_path).dispatch();
|
||||
if exists {
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let mut path = static_root().join(path);
|
||||
if path.is_dir() {
|
||||
path = path.join("index.html");
|
||||
}
|
||||
|
||||
let mut file = File::open(path).expect("open file");
|
||||
let mut expected_contents = String::new();
|
||||
file.read_to_string(&mut expected_contents).expect("read file");
|
||||
assert_eq!(response.into_string(), Some(expected_contents));
|
||||
} else {
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_all(client: &Client, prefix: &str, paths: &[&str], exist: bool) {
|
||||
for path in paths.iter() {
|
||||
assert_file(client, prefix, path, exist);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_no_index() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "no_index", REGULAR_FILES, true);
|
||||
assert_all(&client, "no_index", HIDDEN_FILES, false);
|
||||
assert_all(&client, "no_index", INDEXED_DIRECTORIES, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_hidden() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "dots", REGULAR_FILES, true);
|
||||
assert_all(&client, "dots", HIDDEN_FILES, true);
|
||||
assert_all(&client, "dots", INDEXED_DIRECTORIES, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_index() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "index", REGULAR_FILES, true);
|
||||
assert_all(&client, "index", HIDDEN_FILES, false);
|
||||
assert_all(&client, "index", INDEXED_DIRECTORIES, true);
|
||||
|
||||
assert_all(&client, "default", REGULAR_FILES, true);
|
||||
assert_all(&client, "default", HIDDEN_FILES, false);
|
||||
assert_all(&client, "default", INDEXED_DIRECTORIES, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_all() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
assert_all(&client, "both", REGULAR_FILES, true);
|
||||
assert_all(&client, "both", HIDDEN_FILES, true);
|
||||
assert_all(&client, "both", INDEXED_DIRECTORIES, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranking() {
|
||||
let root = static_root();
|
||||
for rank in -128..128 {
|
||||
let a = FileServer::new(&root, Options::None).rank(rank);
|
||||
let b = FileServer::from(&root).rank(rank);
|
||||
|
||||
for handler in vec![a, b] {
|
||||
let routes: Vec<Route> = handler.into();
|
||||
assert!(routes.iter().all(|route| route.rank == rank), "{}", rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forwarding() {
|
||||
use rocket::{get, routes};
|
||||
|
||||
#[get("/<value>", rank = 20)]
|
||||
fn catch_one(value: String) -> String { value }
|
||||
|
||||
#[get("/<a>/<b>", rank = 20)]
|
||||
fn catch_two(a: &str, b: &str) -> String { format!("{}/{}", a, b) }
|
||||
|
||||
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
|
||||
let client = Client::debug(rocket).expect("valid rocket");
|
||||
|
||||
let response = client.get("/default/ireallydontexist").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.into_string().unwrap(), "ireallydontexist");
|
||||
|
||||
let response = client.get("/default/idont/exist").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.into_string().unwrap(), "idont/exist");
|
||||
|
||||
assert_all(&client, "both", REGULAR_FILES, true);
|
||||
assert_all(&client, "both", HIDDEN_FILES, true);
|
||||
assert_all(&client, "both", INDEXED_DIRECTORIES, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_redirection() {
|
||||
let client = Client::debug(rocket()).expect("valid rocket");
|
||||
|
||||
// Redirection only happens if enabled, and doesn't affect index behaviour.
|
||||
let response = client.get("/no_index/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client.get("/index/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let response = client.get("/redir/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir/inner/"));
|
||||
|
||||
let response = client.get("/redir/inner?foo=bar").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir/inner/?foo=bar"));
|
||||
|
||||
let response = client.get("/redir_index/inner").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir_index/inner/"));
|
||||
|
||||
// Paths with trailing slash are unaffected.
|
||||
let response = client.get("/redir/inner/").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client.get("/redir_index/inner/").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
// Root of route is also redirected.
|
||||
let response = client.get("/no_index").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client.get("/index").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let response = client.get("/redir").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir/"));
|
||||
|
||||
let response = client.get("/redir_index").dispatch();
|
||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||
assert_eq!(response.headers().get("Location").next(), Some("/redir_index/"));
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
macro_rules! crate_relative {
|
||||
macro_rules! relative {
|
||||
($path:expr) => {
|
||||
std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path))
|
||||
};
|
||||
|
@ -9,8 +9,8 @@ fn tls_config_from_soruce() {
|
|||
use rocket::config::{Config, TlsConfig};
|
||||
use rocket::figment::Figment;
|
||||
|
||||
let cert_path = crate_relative!("examples/tls/private/cert.pem");
|
||||
let key_path = crate_relative!("examples/tls/private/key.pem");
|
||||
let cert_path = relative!("examples/tls/private/cert.pem");
|
||||
let key_path = relative!("examples/tls/private/key.pem");
|
||||
|
||||
let rocket_config = Config {
|
||||
tls: Some(TlsConfig::from_paths(cert_path, key_path)),
|
||||
|
|
|
@ -67,8 +67,8 @@ This directory contains projects showcasing Rocket's features.
|
|||
operations. Uses managed state to implement a simple index hit counter. Also
|
||||
uses managed state to store, retrieve, and push/pop from a concurrent queue.
|
||||
|
||||
* **[`static-files`](./static-files)** - Uses `contrib` `StaticFiles` serve
|
||||
static files. Also creates a `second` manual yet safe version.
|
||||
* **[`static-files`](./static-files)** - Uses `FileServer` to serve static
|
||||
files. Also creates a `second` manual yet safe version.
|
||||
|
||||
* **[`templating`](./templating)** - Illustrates using `contrib` `templates`
|
||||
support with identical examples for handlebars and tera.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#[macro_use]extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::http::{Status, ContentType};
|
||||
use rocket::form::{Form, Contextual, FromForm, FromFormField, Context};
|
||||
use rocket::data::TempFile;
|
||||
use rocket::fs::{FileServer, relative};
|
||||
|
||||
use rocket_contrib::serve::{StaticFiles, crate_relative};
|
||||
use rocket_contrib::templates::Template;
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
|
@ -85,5 +85,5 @@ fn rocket() -> _ {
|
|||
rocket::build()
|
||||
.mount("/", routes![index, submit])
|
||||
.attach(Template::fairing())
|
||||
.mount("/", StaticFiles::from(crate_relative!("/static")))
|
||||
.mount("/", FileServer::from(relative!("/static")))
|
||||
}
|
||||
|
|
|
@ -7,4 +7,3 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
rocket = { path = "../../core/lib" }
|
||||
rocket_contrib = { path = "../../contrib/lib" }
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#[cfg(test)] mod tests;
|
||||
|
||||
use rocket_contrib::serve::{StaticFiles, crate_relative};
|
||||
use rocket::fs::{FileServer, relative};
|
||||
|
||||
// If we wanted or needed to serve files manually, we'd use `NamedFile`. Always
|
||||
// prefer to use `StaticFiles`!
|
||||
// prefer to use `FileServer`!
|
||||
mod manual {
|
||||
use std::path::{PathBuf, Path};
|
||||
use rocket::response::NamedFile;
|
||||
|
||||
#[rocket::get("/second/<path..>")]
|
||||
pub async fn second(path: PathBuf) -> Option<NamedFile> {
|
||||
let mut path = Path::new(super::crate_relative!("static")).join(path);
|
||||
let mut path = Path::new(super::relative!("static")).join(path);
|
||||
if path.is_dir() {
|
||||
path.push("index.html");
|
||||
}
|
||||
|
@ -23,5 +23,5 @@ mod manual {
|
|||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.mount("/", rocket::routes![manual::second])
|
||||
.mount("/", StaticFiles::from(crate_relative!("static")))
|
||||
.mount("/", FileServer::from(relative!("static")))
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@ rand = "0.8"
|
|||
[dependencies.rocket_contrib]
|
||||
path = "../../contrib/lib"
|
||||
default_features = false
|
||||
features = ["tera_templates", "diesel_sqlite_pool", "serve"]
|
||||
features = ["tera_templates", "diesel_sqlite_pool"]
|
||||
|
|
|
@ -13,9 +13,9 @@ use rocket::request::FlashMessage;
|
|||
use rocket::response::{Flash, Redirect};
|
||||
use rocket::serde::Serialize;
|
||||
use rocket::form::Form;
|
||||
use rocket::fs::{FileServer, relative};
|
||||
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_contrib::serve::{StaticFiles, crate_relative};
|
||||
|
||||
use crate::task::{Task, Todo};
|
||||
|
||||
|
@ -110,7 +110,7 @@ fn rocket() -> _ {
|
|||
.attach(DbConn::fairing())
|
||||
.attach(Template::fairing())
|
||||
.attach(AdHoc::on_ignite("Run Migrations", run_migrations))
|
||||
.mount("/", StaticFiles::from(crate_relative!("static")))
|
||||
.mount("/", FileServer::from(relative!("static")))
|
||||
.mount("/", routes![index])
|
||||
.mount("/todo", routes![new, toggle, delete])
|
||||
}
|
||||
|
|
|
@ -62,7 +62,6 @@ function test_contrib() {
|
|||
FEATURES=(
|
||||
tera_templates
|
||||
handlebars_templates
|
||||
serve
|
||||
diesel_postgres_pool
|
||||
diesel_sqlite_pool
|
||||
diesel_mysql_pool
|
||||
|
|
|
@ -154,13 +154,12 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
|
|||
! tip: Rocket makes it even _easier_ to serve static files!
|
||||
|
||||
If you need to serve static files from your Rocket application, consider using
|
||||
the [`StaticFiles`] custom handler from [`rocket_contrib`], which makes it as
|
||||
simple as:
|
||||
[`FileServer`], which makes it as simple as:
|
||||
|
||||
`rocket.mount("/public", StaticFiles::from("static/"))`
|
||||
`rocket.mount("/public", FileServer::from("static/"))`
|
||||
|
||||
[`rocket_contrib`]: @api/rocket_contrib/
|
||||
[`StaticFiles`]: @api/rocket_contrib/serve/struct.StaticFiles.html
|
||||
[`FileServer`]: @api/rocket/fs/struct.FileServer.html
|
||||
[`FromSegments`]: @api/rocket/request/trait.FromSegments.html
|
||||
|
||||
## Forwarding
|
||||
|
|
Loading…
Reference in New Issue