Open remko opened 8 years ago
Yep, this seems like it will be a common pattern for frontend-heavy web applications. If a static file exists, serve it, otherwise serve the HTML & JS for the application, which will handle the routing.
One complication I see is that we may want Iron to be able to check the URL against a whitelist to know if it is a valid path. If it isn't, then Iron should return a 404. After sketching some pseudocode, I thought of this:
let static_files = Static::new("assets");
let remap = Remap::new("assets/index.html")
.get("/users")
.get("/users/:id")
.get("/posts")
.get("/posts/:id");
let fallback = Fallback::new(static_files, remap);
This seemingly allows the composition currently favored by the Iron project. Perhaps the Fallback
may not even be needed, if Remap
is a Modifier
which can then simply allow Static
to do the normal flow...
Something similar to this appears to work:
remap.rs
use std::collections::HashSet;
use iron::prelude::*;
use iron::{BeforeMiddleware, Url};
pub struct Remap {
new_path: String,
routes: HashSet<&'static str>,
}
impl Remap {
pub fn new(path: &str) -> Remap {
Remap {
new_path: path.into(),
routes: HashSet::new(),
}
}
pub fn get(&mut self, path: &'static str) {
self.routes.insert(path);
}
}
impl BeforeMiddleware for Remap {
fn before(&self, req: &mut Request) -> IronResult<()> {
// TODO: do we really need to clone?
let original_url = req.url.clone().into_generic_url();
if self.routes.contains(original_url.path()) {
let mut changed_url = original_url;
changed_url.set_path(&self.new_path);
let changed_url = Url::from_generic_url(changed_url).expect("Unable to rebuild URL");
req.url = changed_url;
}
Ok(())
}
}
main.rs
mod remap;
use remap::Remap;
fn main() {
let mut mount = Mount::new();
mount.mount("/", Static::new(&root));
// Routes that are handled client-side
let mut remap = Remap::new("/");
remap.get("/about");
let mut remapped = Chain::new(mount);
remapped.link_before(remap);
info!("Starting the server on {}:{}", address, port);
Iron::new(remapped).http((&*address, port)).expect("Unable to start server");
}
To be more complete, it should use the same underlying glob and route recognizer libraries as Iron to allow for the syntax to match.
You can write a handler which tries two handlers and returns the first one that doesn't 404.
You can write a handler which tries two handlers and returns the first one that doesn't 404.
And here's another solution, a year later
struct Fallback;
impl AroundMiddleware for Fallback {
fn around(self, handler: Box<Handler>) -> Box<Handler> {
Box::new(FallbackHandler(handler))
}
}
struct FallbackHandler(Box<Handler>);
impl Handler for FallbackHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let resp = self.0.handle(req);
match resp {
Err(err) => {
match err.response.status {
Some(status::NotFound) => {
let file = File::open("/tmp/example").unwrap();
Ok(Response::with((status::Ok, file)))
}
_ => Err(err),
}
}
other => other
}
}
}
maybe it's worth to create middleware's or alike repository under /iron organization to stock these treasures?
Hi,
I have several web apps where I have a chain where I first try to serve a static file, and if it doesn't exist, fallback to another handler. What's the recommended way to use such a fallback mechanism with staticfile? Right now, i defined my own custom handler that implements this cascading by delegating to static and catching 404, but I was wondering whether this is something that can be done without a custom handler; is this something that 'static' could implement? (i.e. a constructor that takes a path and a handler to call when the file isn't on the filesystem?
thanks, Remko