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 rocket::{Request, Data, Route};
|
||||
use rocket::http::{Method, Status, uri::Segments};
|
||||
use rocket::http::{Method, uri::Segments};
|
||||
use rocket::handler::{Handler, Outcome};
|
||||
use rocket::response::NamedFile;
|
||||
use rocket::outcome::IntoOutcome;
|
||||
|
||||
/// 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
|
||||
/// [`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.
|
||||
/// 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()`]. Additionally, the rank of generate routes, which
|
||||
/// defaults to `10`, can be set via the [`StaticFiles::rank()`] builder method.
|
||||
/// [`StaticFiles::new()`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// To serve files from the `/static` directory 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:
|
||||
/// To serve files from the `/static` local file system directory 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
|
||||
/// # 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
|
||||
/// 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`.
|
||||
/// 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`.
|
||||
///
|
||||
/// 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
|
||||
|
@ -272,13 +273,14 @@ impl Into<Vec<Route>> for StaticFiles {
|
|||
}
|
||||
|
||||
impl Handler for StaticFiles {
|
||||
fn handle<'r>(&self, req: &'r Request<'_>, _: Data) -> Outcome<'r> {
|
||||
fn handle_index<'r>(opt: Options, r: &'r Request<'_>, path: &Path) -> Outcome<'r> {
|
||||
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
|
||||
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> Outcome<'r> {
|
||||
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
|
||||
|
@ -286,7 +288,7 @@ impl Handler for StaticFiles {
|
|||
let current_route = req.route().expect("route while handling");
|
||||
let is_segments_route = current_route.uri.path().ends_with(">");
|
||||
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`,
|
||||
|
@ -295,13 +297,12 @@ impl Handler for StaticFiles {
|
|||
let path = req.get_segments::<Segments<'_>>(0)
|
||||
.and_then(|res| res.ok())
|
||||
.and_then(|segments| segments.into_path_buf(allow_dotfiles).ok())
|
||||
.map(|path| self.root.join(path))
|
||||
.into_outcome(Status::NotFound)?;
|
||||
.map(|path| self.root.join(path));
|
||||
|
||||
if path.is_dir() {
|
||||
handle_index(self.options, req, &path)
|
||||
} else {
|
||||
Outcome::from(req, NamedFile::open(&path).ok())
|
||||
match &path {
|
||||
Some(path) if path.is_dir() => handle_dir(self.options, req, data, path),
|
||||
Some(path) => Outcome::from_or_forward(req, data, 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
|
||||
/// equivalent to `Outcome::Failure(code)`.
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue