Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static with fallback #78

Open
remko opened this issue Jul 10, 2016 · 5 comments
Open

Static with fallback #78

remko opened this issue Jul 10, 2016 · 5 comments

Comments

@remko
Copy link

remko commented Jul 10, 2016

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

@shepmaster
Copy link
Contributor

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...

@shepmaster
Copy link
Contributor

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.

@untitaker
Copy link
Member

You can write a handler which tries two handlers and returns the first one that doesn't 404.

@shepmaster
Copy link
Contributor

shepmaster commented Sep 27, 2017

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
        }
    }
}

@ernestas-poskus
Copy link

maybe it's worth to create middleware's or alike repository under /iron organization to stock these treasures?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants