Skip to content

Commit 21b1017

Browse files
committed
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 rwf2#1036.
1 parent c100a92 commit 21b1017

File tree

3 files changed

+77
-23
lines changed

3 files changed

+77
-23
lines changed

contrib/lib/src/serve.rs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
use std::path::{PathBuf, Path};
1818

1919
use rocket::{Request, Data, Route};
20-
use rocket::http::{Method, Status, uri::Segments};
20+
use rocket::http::{Method, uri::Segments};
2121
use rocket::handler::{Handler, Outcome};
2222
use rocket::response::NamedFile;
23-
use rocket::outcome::IntoOutcome;
2423

2524
/// A bitset representing configurable options for the [`StaticFiles`] handler.
2625
///
@@ -101,19 +100,21 @@ impl std::ops::BitOr for Options {
101100
/// local file system. To use it, construct a `StaticFiles` using either
102101
/// [`StaticFiles::from()`] or [`StaticFiles::new()`] then simply `mount` the
103102
/// handler at a desired path. When mounted, the handler will generate route(s)
104-
/// that serve the desired static files.
103+
/// that serve the desired static files. If a requested file is not found, the
104+
/// routes _forward_ the incoming request. The default rank of the generated
105+
/// routes is `10`. To customize route ranking, use the [`StaticFiles::rank()`]
106+
/// method.
105107
///
106108
/// # Options
107109
///
108110
/// The handler's functionality can be customized by passing an [`Options`] to
109-
/// [`StaticFiles::new()`]. Additionally, the rank of generate routes, which
110-
/// defaults to `10`, can be set via the [`StaticFiles::rank()`] builder method.
111+
/// [`StaticFiles::new()`].
111112
///
112113
/// # Example
113114
///
114-
/// To serve files from the `/static` directory at the `/public` path, allowing
115-
/// `index.html` files to be used to respond to requests for a directory (the
116-
/// default), you might write the following:
115+
/// To serve files from the `/static` local file system directory at the
116+
/// `/public` path, allowing `index.html` files to be used to respond to
117+
/// requests for a directory (the default), you might write the following:
117118
///
118119
/// ```rust
119120
/// # extern crate rocket;
@@ -129,10 +130,10 @@ impl std::ops::BitOr for Options {
129130
/// }
130131
/// ```
131132
///
132-
/// With this set-up, requests for files at `/public/<path..>` will be handled
133-
/// by returning the contents of `/static/<path..>`. Requests for _directories_
134-
/// at `/public/<directory>` will be handled by returning the contents of
135-
/// `/static/<directory>/index.html`.
133+
/// With this, requests for files at `/public/<path..>` will be handled by
134+
/// returning the contents of `./static/<path..>`. Requests for _directories_ at
135+
/// `/public/<directory>` will be handled by returning the contents of
136+
/// `./static/<directory>/index.html`.
136137
///
137138
/// If your static files are stored relative to your crate and your project is
138139
/// managed by Cargo, you should either use a relative path and ensure that your
@@ -272,21 +273,22 @@ impl Into<Vec<Route>> for StaticFiles {
272273
}
273274

274275
impl Handler for StaticFiles {
275-
fn handle<'r>(&self, req: &'r Request<'_>, _: Data) -> Outcome<'r> {
276-
fn handle_index<'r>(opt: Options, r: &'r Request<'_>, path: &Path) -> Outcome<'r> {
276+
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
277+
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> Outcome<'r> {
277278
if !opt.contains(Options::Index) {
278-
return Outcome::failure(Status::NotFound);
279+
return Outcome::forward(d);
279280
}
280281

281-
Outcome::from(r, NamedFile::open(path.join("index.html")).ok())
282+
let file = NamedFile::open(path.join("index.html")).ok();
283+
Outcome::from_or_forward(r, d, file)
282284
}
283285

284286
// If this is not the route with segments, handle it only if the user
285287
// requested a handling of index files.
286288
let current_route = req.route().expect("route while handling");
287289
let is_segments_route = current_route.uri.path().ends_with(">");
288290
if !is_segments_route {
289-
return handle_index(self.options, req, &self.root);
291+
return handle_dir(self.options, req, data, &self.root);
290292
}
291293

292294
// Otherwise, we're handling segments. Get the segments as a `PathBuf`,
@@ -295,13 +297,12 @@ impl Handler for StaticFiles {
295297
let path = req.get_segments::<Segments<'_>>(0)
296298
.and_then(|res| res.ok())
297299
.and_then(|segments| segments.into_path_buf(allow_dotfiles).ok())
298-
.map(|path| self.root.join(path))
299-
.into_outcome(Status::NotFound)?;
300+
.map(|path| self.root.join(path));
300301

301-
if path.is_dir() {
302-
handle_index(self.options, req, &path)
303-
} else {
304-
Outcome::from(req, NamedFile::open(&path).ok())
302+
match &path {
303+
Some(path) if path.is_dir() => handle_dir(self.options, req, data, path),
304+
Some(path) => Outcome::from_or_forward(req, data, NamedFile::open(path).ok()),
305+
None => Outcome::forward(data)
305306
}
306307
}
307308
}

contrib/lib/tests/static_files.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,31 @@ mod static_tests {
116116
}
117117
}
118118
}
119+
120+
#[test]
121+
fn test_forwarding() {
122+
use rocket::http::RawStr;
123+
use rocket::{get, routes};
124+
125+
#[get("/<value>", rank = 20)]
126+
fn catch_one(value: String) -> String { value }
127+
128+
#[get("/<a>/<b>", rank = 20)]
129+
fn catch_two(a: &RawStr, b: &RawStr) -> String { format!("{}/{}", a, b) }
130+
131+
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
132+
let client = Client::new(rocket).expect("valid rocket");
133+
134+
let mut response = client.get("/default/ireallydontexist").dispatch();
135+
assert_eq!(response.status(), Status::Ok);
136+
assert_eq!(response.body_string().unwrap(), "ireallydontexist");
137+
138+
let mut response = client.get("/default/idont/exist").dispatch();
139+
assert_eq!(response.status(), Status::Ok);
140+
assert_eq!(response.body_string().unwrap(), "idont/exist");
141+
142+
assert_all(&client, "both", REGULAR_FILES, true);
143+
assert_all(&client, "both", HIDDEN_FILES, true);
144+
assert_all(&client, "both", INDEXED_DIRECTORIES, true);
145+
}
119146
}

core/lib/src/handler.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,32 @@ impl<'r> Outcome<'r> {
206206
}
207207
}
208208

209+
/// Return the `Outcome` of response to `req` from `responder`.
210+
///
211+
/// If the responder returns `Ok`, an outcome of `Success` is
212+
/// returned with the response. If the responder returns `Err`, an
213+
/// outcome of `Forward` is returned.
214+
///
215+
/// # Example
216+
///
217+
/// ```rust
218+
/// use rocket::{Request, Data};
219+
/// use rocket::handler::Outcome;
220+
///
221+
/// fn str_responder(req: &Request, data: Data) -> Outcome<'static> {
222+
/// Outcome::from_or_forward(req, data, "Hello, world!")
223+
/// }
224+
/// ```
225+
#[inline]
226+
pub fn from_or_forward<T>(req: &Request<'_>, data: Data, responder: T) -> Outcome<'r>
227+
where T: Responder<'r>
228+
{
229+
match responder.respond_to(req) {
230+
Ok(response) => outcome::Outcome::Success(response),
231+
Err(_) => outcome::Outcome::Forward(data)
232+
}
233+
}
234+
209235
/// Return an `Outcome` of `Failure` with the status code `code`. This is
210236
/// equivalent to `Outcome::Failure(code)`.
211237
///

0 commit comments

Comments
 (0)