Rocket

A web framework for Rust. https://rocket.rs (mirror)
git clone git://git.thc420.xyz/Rocket
Log | Files | Refs | README

commit 21b10176ee5d6ba89bf84e1a89dd7a0c1bb7ce4e
parent c100a9212735928ed5ada2a38754a21247c86677
Author: Sergio Benitez <sb@sergio.bz>
Date:   Sun,  7 Jul 2019 23:21:13 -0700

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.

Diffstat:
Mcontrib/lib/src/serve.rs | 47++++++++++++++++++++++++-----------------------
Mcontrib/lib/tests/static_files.rs | 27+++++++++++++++++++++++++++
Mcore/lib/src/handler.rs | 26++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 23 deletions(-)

diff --git a/contrib/lib/src/serve.rs b/contrib/lib/src/serve.rs @@ -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) } } } diff --git a/contrib/lib/tests/static_files.rs b/contrib/lib/tests/static_files.rs @@ -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); + } } diff --git a/core/lib/src/handler.rs b/core/lib/src/handler.rs @@ -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)`. ///