如何通过迭代路由向量在Rouille中设置路由

时间:2017-01-03 16:05:07

标签: rust

Rouille hello world example显示了如何将router!宏用于固定路线集。

以下示例代码说明了需要能够从数据库或可插入代码“引导”路由 - 我目前可以使用Iron Web框架执行此操作:

pub struct Route {
    pub http_method: String,
    pub url_path: String,
    pub callback_func: fn(_: &mut Request) -> IronResult<Response>,
}

impl Route {
    pub fn new(m: String, u: String, f: fn(_: &mut Request) -> IronResult<Response>) -> Route {
        Route {
            http_method: m,
            url_path: u,
            callback_func: f,
        }
    }
}

fn main() {
    // router is an Iron middleware
    let mut router = Router::new();

    // prepare routes for bootstrapping into the Iron router
    let r1 = Route::new("get".to_string(), "/*".to_string(), my_callback_func);
    let r2 = Route::new("get".to_string(), "/".to_string(), my_callback_func);

    let mut routes = Vec::new();
    routes.push(r1);
    routes.push(r2);

    for route in routes.iter_mut() {
        if route.http_method == "get" {
            // passes each route to the Iron router
            router.get(&route.url_path, (&*route).callback_func);
        } // else if, put, post, delete...
    }

    Iron::new(router).http("localhost:3000").unwrap();
}

fn my_callback_func(_: &mut Request) -> IronResult<Response> {
    //...
}

(Playground)

虽然我正在阅读Rust中的宏,但我对Rouille's router! macro,Rust或宏一般都没有足够的了解,以弄清楚如何用Rouille实现等效。

1 个答案:

答案 0 :(得分:2)

如果你检查the main source of the router macro,那就太长了,但并不是非常复杂:

($request:expr, $(($method:ident) ($($pat:tt)+) => $value:block,)* _ => $def:expr) => {
    {
        let request = &$request;

        // ignoring the GET parameters (everything after `?`)
        let request_url = request.url();
        let request_url = {
            let pos = request_url.find('?').unwrap_or(request_url.len());
            &request_url[..pos]
        };

        let mut ret = None;

        $({
            if ret.is_none() && request.method() == stringify!($method) {
                ret = router!(__check_pattern request_url $value $($pat)+);
            }
        })+

        if let Some(ret) = ret {
            ret
        } else {
            $def
        }
    }
};

简而言之,它需要一个请求,零或多个模式匹配,以及默认值。它获取了URL,然后调度到宏的其他部分,以查看URL是否与路径匹配,并使用一些递归技巧来定义一些带有路径组件的变量。无论哪个臂首先匹配都将设置返回值,如果没有匹配,将使用默认值。

不幸的是,宏期望方法和路径ident,所以基本上你不能将它与表达式一起使用。这意味着我们无法传递像"foo"这样的变量或文字。这使你很难。

因此,我们会做所有优秀程序员所做的事情:复制并粘贴代码。从宏中提取块并重新调整它们:

#[macro_use]
extern crate rouille;

use rouille::Request;
use rouille::Response;

struct Route(&'static str, &'static str, fn(&Request) -> Response);

fn main() {
    let routes = [
        Route("GET", "/one", do_one),
        Route("GET", "/two", do_two),
    ];

    rouille::start_server("0.0.0.0:9080", move |request| {
        let mut result = None;

        let request = &request;

        // ignoring the GET parameters (everything after `?`)
        let request_url = request.url();
        let request_url = {
            let pos = request_url.find('?').unwrap_or(request_url.len());
            &request_url[..pos]
        };

        for &Route(method, path, f) in &routes {
            if result.is_none() {
                // This checking of the path is terrible, limited, and hacky
                if request.method() == method && request_url.ends_with(path) {
                    result = Some(f(request));
                }
            }
        }

        result.unwrap_or_else(|| Response::text("Default!"))
    });
}

fn do_one(_: &Request) -> Response {
    Response::text("hello world one")
}

fn do_two(_: &Request) -> Response {
    Response::text("hello world two")
}

这会运行/one/two以及其他所有内容的各种处理程序。

我不是Rouille的专家,事实上我从来没有在今天之前使用过它,但看起来你似乎正试图将它用于超出目前设计目标的东西。也许这是故意的,作者试图提出一个非常自以为是的工具。也许这是偶然的,他们没有想到这个用例。也许这是暂时的,他们只是没有接触到它。

无论哪种方式,我建议问作者。如果它不是预期的用例,他们可以更新项目文档以明确说明。否则,他们可能会产生问题来实现该功能,您可以帮助设计它。