mirror of https://github.com/rwf2/Rocket.git
Forward from 'StaticFiles' if a file is not found.
Also adds a 'handler::Outcome::from_or_forward' method for easily constructing handler outcomes that forward on responder failures. Fixes #1036.
This commit is contained in:
parent
c100a92127
commit
21b10176ee
|
@ -17,10 +17,9 @@
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
|
|
||||||
use rocket::{Request, Data, Route};
|
use rocket::{Request, Data, Route};
|
||||||
use rocket::http::{Method, Status, uri::Segments};
|
use rocket::http::{Method, uri::Segments};
|
||||||
use rocket::handler::{Handler, Outcome};
|
use rocket::handler::{Handler, Outcome};
|
||||||
use rocket::response::NamedFile;
|
use rocket::response::NamedFile;
|
||||||
use rocket::outcome::IntoOutcome;
|
|
||||||
|
|
||||||
/// A bitset representing configurable options for the [`StaticFiles`] handler.
|
/// A bitset representing configurable options for the [`StaticFiles`] handler.
|
||||||
///
|
///
|
||||||
|
@ -101,19 +100,21 @@ impl std::ops::BitOr for Options {
|
||||||
/// local file system. To use it, construct a `StaticFiles` using either
|
/// local file system. To use it, construct a `StaticFiles` using either
|
||||||
/// [`StaticFiles::from()`] or [`StaticFiles::new()`] then simply `mount` the
|
/// [`StaticFiles::from()`] or [`StaticFiles::new()`] then simply `mount` the
|
||||||
/// handler at a desired path. When mounted, the handler will generate route(s)
|
/// handler at a desired path. When mounted, the handler will generate route(s)
|
||||||
/// that serve the desired static files.
|
/// 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
|
/// # Options
|
||||||
///
|
///
|
||||||
/// The handler's functionality can be customized by passing an [`Options`] to
|
/// The handler's functionality can be customized by passing an [`Options`] to
|
||||||
/// [`StaticFiles::new()`]. Additionally, the rank of generate routes, which
|
/// [`StaticFiles::new()`].
|
||||||
/// defaults to `10`, can be set via the [`StaticFiles::rank()`] builder method.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// To serve files from the `/static` directory at the `/public` path, allowing
|
/// To serve files from the `/static` local file system directory at the
|
||||||
/// `index.html` files to be used to respond to requests for a directory (the
|
/// `/public` path, allowing `index.html` files to be used to respond to
|
||||||
/// default), you might write the following:
|
/// requests for a directory (the default), you might write the following:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
|
@ -129,10 +130,10 @@ impl std::ops::BitOr for Options {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// With this set-up, requests for files at `/public/<path..>` will be handled
|
/// With this, requests for files at `/public/<path..>` will be handled by
|
||||||
/// by returning the contents of `/static/<path..>`. Requests for _directories_
|
/// returning the contents of `./static/<path..>`. Requests for _directories_ at
|
||||||
/// at `/public/<directory>` will be handled by returning the contents of
|
/// `/public/<directory>` will be handled by returning the contents of
|
||||||
/// `/static/<directory>/index.html`.
|
/// `./static/<directory>/index.html`.
|
||||||
///
|
///
|
||||||
/// If your static files are stored relative to your crate and your project is
|
/// If your static files are stored relative to your crate and your project is
|
||||||
/// managed by Cargo, you should either use a relative path and ensure that your
|
/// managed by Cargo, you should either use a relative path and ensure that your
|
||||||
|
@ -272,13 +273,14 @@ impl Into<Vec<Route>> for StaticFiles {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for StaticFiles {
|
impl Handler for StaticFiles {
|
||||||
fn handle<'r>(&self, req: &'r Request<'_>, _: Data) -> Outcome<'r> {
|
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
|
||||||
fn handle_index<'r>(opt: Options, r: &'r Request<'_>, path: &Path) -> Outcome<'r> {
|
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> Outcome<'r> {
|
||||||
if !opt.contains(Options::Index) {
|
if !opt.contains(Options::Index) {
|
||||||
return Outcome::failure(Status::NotFound);
|
return Outcome::forward(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
Outcome::from(r, NamedFile::open(path.join("index.html")).ok())
|
let file = NamedFile::open(path.join("index.html")).ok();
|
||||||
|
Outcome::from_or_forward(r, d, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is not the route with segments, handle it only if the user
|
// If this is not the route with segments, handle it only if the user
|
||||||
|
@ -286,7 +288,7 @@ impl Handler for StaticFiles {
|
||||||
let current_route = req.route().expect("route while handling");
|
let current_route = req.route().expect("route while handling");
|
||||||
let is_segments_route = current_route.uri.path().ends_with(">");
|
let is_segments_route = current_route.uri.path().ends_with(">");
|
||||||
if !is_segments_route {
|
if !is_segments_route {
|
||||||
return handle_index(self.options, req, &self.root);
|
return handle_dir(self.options, req, data, &self.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we're handling segments. Get the segments as a `PathBuf`,
|
// Otherwise, we're handling segments. Get the segments as a `PathBuf`,
|
||||||
|
@ -295,13 +297,12 @@ impl Handler for StaticFiles {
|
||||||
let path = req.get_segments::<Segments<'_>>(0)
|
let path = req.get_segments::<Segments<'_>>(0)
|
||||||
.and_then(|res| res.ok())
|
.and_then(|res| res.ok())
|
||||||
.and_then(|segments| segments.into_path_buf(allow_dotfiles).ok())
|
.and_then(|segments| segments.into_path_buf(allow_dotfiles).ok())
|
||||||
.map(|path| self.root.join(path))
|
.map(|path| self.root.join(path));
|
||||||
.into_outcome(Status::NotFound)?;
|
|
||||||
|
|
||||||
if path.is_dir() {
|
match &path {
|
||||||
handle_index(self.options, req, &path)
|
Some(path) if path.is_dir() => handle_dir(self.options, req, data, path),
|
||||||
} else {
|
Some(path) => Outcome::from_or_forward(req, data, NamedFile::open(path).ok()),
|
||||||
Outcome::from(req, NamedFile::open(&path).ok())
|
None => Outcome::forward(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,4 +116,31 @@ mod static_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_forwarding() {
|
||||||
|
use rocket::http::RawStr;
|
||||||
|
use rocket::{get, routes};
|
||||||
|
|
||||||
|
#[get("/<value>", rank = 20)]
|
||||||
|
fn catch_one(value: String) -> String { value }
|
||||||
|
|
||||||
|
#[get("/<a>/<b>", rank = 20)]
|
||||||
|
fn catch_two(a: &RawStr, b: &RawStr) -> String { format!("{}/{}", a, b) }
|
||||||
|
|
||||||
|
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
|
||||||
|
let client = Client::new(rocket).expect("valid rocket");
|
||||||
|
|
||||||
|
let mut response = client.get("/default/ireallydontexist").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
assert_eq!(response.body_string().unwrap(), "ireallydontexist");
|
||||||
|
|
||||||
|
let mut response = client.get("/default/idont/exist").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
assert_eq!(response.body_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,32 @@ impl<'r> Outcome<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the `Outcome` of response to `req` from `responder`.
|
||||||
|
///
|
||||||
|
/// If the responder returns `Ok`, an outcome of `Success` is
|
||||||
|
/// returned with the response. If the responder returns `Err`, an
|
||||||
|
/// outcome of `Forward` is returned.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::{Request, Data};
|
||||||
|
/// use rocket::handler::Outcome;
|
||||||
|
///
|
||||||
|
/// fn str_responder(req: &Request, data: Data) -> Outcome<'static> {
|
||||||
|
/// Outcome::from_or_forward(req, data, "Hello, world!")
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn from_or_forward<T>(req: &Request<'_>, data: Data, responder: T) -> Outcome<'r>
|
||||||
|
where T: Responder<'r>
|
||||||
|
{
|
||||||
|
match responder.respond_to(req) {
|
||||||
|
Ok(response) => outcome::Outcome::Success(response),
|
||||||
|
Err(_) => outcome::Outcome::Forward(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an `Outcome` of `Failure` with the status code `code`. This is
|
/// Return an `Outcome` of `Failure` with the status code `code`. This is
|
||||||
/// equivalent to `Outcome::Failure(code)`.
|
/// equivalent to `Outcome::Failure(code)`.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue