Ogeon/rustful

Implement proper support for routers in routers

Closed this issue · 5 comments

Ogeon commented

This would enable much more flexibility when it comes to how requests are handled, by allowing composition of multiple router types. The fact that Handler implies Router makes the leap a bit shorter, and the main change is probably to not assume that routers stores handlers, but I'm sure there will be rings on the water.

I did something like this for a test application with multiple app support:
https://github.com/FerarDuanSednan/pilum

Have a look at those files:

  • src/config/routes.rs
  • src/app/mod.rs
  • src/app2/mod.rs

I encapsulated the specifics enums to a global one like this:

use rustful::{Handler, Context, Response};

use app::AppHandler;
use app2::App2Handler;

pub enum AppRoutes {
    App(AppHandler),
    App2(App2Handler)
}


impl Handler for AppRoutes {

    fn handle_request(&self, context: Context, response: Response) {
        match *self {
            AppRoutes::App(ref a) => a.handle_request(context, response),
            AppRoutes::App2(ref a) => a.handle_request(context, response)
        }
    }

}

I don't know if it will help you implementing this :)

PS: I'm not a native English speaker, sorry if some terms are incorrect :)

Ogeon commented

Sure, that works for selecting a handler post-routing. The idea with this change is to take it a step further and do kind of the same with routers. Here's a sketch of the behavior:

in main_router.find:
    "routers/a" => router_a.find(...),
    "routers/b" => router_b.find(...)
Ogeon commented

To explain further (I wasn't able to write anything longer yesterday): The standard router API assumes that there is only one router that takes the whole URL and spits out a handler. This can be a bit too rigid and makes it harder/less intuitive to implement composite routers, where the first part of the URL is processed by a "main router" and the rest is processed by sub routers (or with some other selection method).

All of this may be trickier than I thought. One problem is that a Handler is a Router that matches anything, while a proper TreeRouter requires an exact match to pick a Handler. It's therefore not possible to just treat handlers as routers. Routers in routers must be treated as branches and not leaves.

P.S. Your English is fine. Don't worry :)

I found an other way to do this without breaking everything : encapsulate the handlers into Boxes.

// app/mod.rs
pub fn get_routes() -> TreeRouter<Box<Handler>> {
    let router = insert_routes! {
        TreeRouter::new() => {
            Get : Box::new(AppHandler::MainController("great")) as Box<Handler>,
            ":person" => Get : Box::new(AppHandler::MainController("great")) as Box<Handler>
        }
    };
    router
}
// app2/mod.rs
pub fn get_routes() -> TreeRouter<Box<Handler>> {
    let router = insert_routes! {
        TreeRouter::new() => {
            Get : Box::new(App2Handler::WebControllerIndex) as Box<Handler>,
            ":person" => Get : Box::new(App2Handler::WebControllerIndex) as Box<Handler>
        }
    };
    router
}
// config/routes.rs
pub fn get_routes() -> TreeRouter<Box<Handler>> {
    let mut router = TreeRouter::new();
    router.insert_router("/", app::get_routes());
    router.insert_router("/app2", app2::get_routes());
    router
}

I don't know if it's possible to add this to the core ( or to update the insert_routes! macro to make things easier ).

Ogeon commented

Yeah, that works as long as we are dealing with only TreeRouter, but what if you want to plug in some MySpecialRouter as a child router? That's when things becomes less ergonomic. The TreeRouter is already built as a recursive structure, which works because it has access to the content of its children, but attaching an arbitrary router introduces a black box into the system.