path, method和自定义请求方法
path 是 warp 中的路由系统, 一个 web 框架的灵魂所在, 一个优美的路由系统可以给我们带来非常良好的使用体验, 而 warp 的路由体验本身就是非常 nice 的。在本文中将展示一个 RESTful 风格的 API 设计。下面先来学习一下 path 模块。
path 模块
path 文档如下所示:
- path 是匹配路由的方法,path! 是一个宏,它能更简单的来匹配路由,但是它的限制比较少,在实际使用中,我们更偏向于使用 path 方法。
- param 是提取路径参数的方法,例如 user/12345, 使用 param 方法可以获取到路径参数 12345;
- end 用来指定路径匹配结束,例如 user/12345/321 将是一个无效的路径。
这个模块中还有其他的一些结构体和方法,具体可以参考文档。
method 模块
method 模块处理请求的 HTTP 方法部分,如果请求方法不匹配,将拒绝请求 并带有返回 405 Method Not Allowed. 这个模块提供了常见的 HTTP 请求方法,如下图所示:
我们在使用 filter 的时候,通常指定某个路由上的 method 来进行 RESTFul API设计。
自定义请求方法
在 warp 中自定义请求方法也非常简单,使用如下的代码段即可实现。
use warp::{hyper::Method, reject, Filter, Rejection, Reply};
// 定义CREATE 和 LOGOUT 方法
const CREATE_METHOD: &'static str = "CREATE";
const LOGOUT_METHOD: &'static str = "LOGOUT";
// 定义Method ERROR,为其实现 Reject 即可自动实现 405 Method Not Allowed.
#[derive(Debug)]
struct MethodError;
impl warp::reject::Reject for MethodError {}
// 实现自定义方法函数
fn method(name: &'static str) -> impl Filter<Extract = (), Error = warp::Rejection> + Clone {
warp::method()
.and_then(move |m: Method| async move {
if m == name {
Ok(())
} else {
Err(warp::reject::custom(MethodError))
}
})
.untuple_one()
}
实现自定义 HTTP Method 是非常简单的,需要的注意的有两点
- 为自定义 Method 实现 Method Error,这要求为其实现 warp::reject::Reject 特征。
- 自定义请求方法函数的返回值必须是 impl Filter<Extract = (), Error = warp::Rejection> + Clone,这样表明返回的是一个 Filter,这也是我们在warp中定义中间件是返回的类型。在下一篇中,我们来介绍一下Filter中的方法。
- 如果需要其他的逻辑,可以在 m == name 的块内实现。
构建RESTful API
有了上面的知识,我们现在来构建一个RESTful 风格的 API。
let user_router = warp::path("user"); // user 模块的根路径
let create_user = user_router
.and(warp::path::end())
.and(method(CREATE_METHOD))
.and_then(create_user);
let login_user = user_router
.and(warp::path::end())
.and(warp::post())
.and_then(login_user);
let logout_user = user_router
.and(warp::path::end())
.and(method(LOGOUT_METHOD))
.and_then(logout_user);
let edit_user = user_router
.and(warp::path::param())
.and(warp::path::end())
.and(warp::put())
.and_then(edit_user);
let delete_user = user_router
.and(warp::path::param())
.and(warp::path::end())
.and(warp::delete())
.and_then(delete_user);
let apis = hello
.or(create_user)
.or(login_user)
.or(logout_user)
.or(edit_user)
.or(delete_user);
上面这段代码我们定义了RESTFul 风格的 API,我们使用了前文提到的 path, method,自定义 method。我们的五个处理函数分别对应5个HTTP方法。
- and_then 方法是用来添加异步函数的,和它对应的是 map 方法,我们在第一篇文章中使用过,map方法是用来添加同步函数的。
- and 方法添加的 Filter 之间的关系是 and 关系(看起来像是废话)
- or 方法添加的 FIlter 之间的关系是 or 关系(看起来像是废话)
函数 | HTTP 方法 |
---|---|
create_user | CREATE |
login_user | POST |
logout_user | LOGOUT |
edit_user | PUT |
delete_user | DELETE |
对应的五个函数实现,如下所示:
async fn create_user() -> Result<impl warp::Reply, warp::Rejection> {
Ok("创建用户".to_string())
}
async fn login_user() -> Result<impl warp::Reply, warp::Rejection> {
Ok("用户登录".to_string())
}
async fn logout_user() -> Result<impl warp::Reply, warp::Rejection> {
Ok("用户退出".to_string())
}
async fn edit_user(id: u32) -> Result<impl warp::Reply, warp::Rejection> {
Ok(format!("修改用户{}信息", id))
}
async fn delete_user(id: u32) -> Result<impl warp::Reply, warp::Rejection> {
Ok(format!("注销用户{}信息", id))
}
- 再次强调,and_then 方法需要一个 Future 对象,而 map 方法需要一个 called Func 对象。它们需要的返回值是 Result<impl warp::Reply, warp::Rejection> 类型。返回值必须是warp 可以 Reply 的。
完整的代码如下所示:
use std::env;
use warp::{Filter, hyper::Method};
#[tokio::main]
async fn main() {
env::set_var("MYAPP_LOG", "INFO");
pretty_env_logger::try_init_timed_custom_env("MYAPP_LOG").expect("logger init failed!");
let log = warp::log("MYAPP_LOG");
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello = warp::path!("hello" / String)
.and(warp::addr::remote())
.and(warp::header("x-forwarded-for"))
.and(warp::header("x-real-ip"))
.map(|name: String, addr: Option<std::net::SocketAddr>, x_forward_for: String, x_real_ip: String|
{
format!("Hello, {}!\nClient IP: {}\nX-Forwarded-For: {}\nX-Real-IP: {}\n",
name, addr.unwrap().to_string(), x_forward_for, x_real_ip)
})
.with(log);
let user_router = warp::path("user"); // user 模块的根路径
let create_user = user_router
.and(warp::path::end())
.and(method(CREATE_METHOD))
.and_then(create_user);
let login_user = user_router
.and(warp::path::end())
.and(warp::post())
.and_then(login_user);
let logout_user = user_router
.and(warp::path::end())
.and(method(LOGOUT_METHOD))
.and_then(logout_user);
let edit_user = user_router
.and(warp::path::param())
.and(warp::path::end())
.and(warp::put())
.and_then(edit_user);
let delete_user = user_router
.and(warp::path::param())
.and(warp::path::end())
.and(warp::delete())
.and_then(delete_user);
let apis = hello
.or(create_user)
.or(login_user)
.or(logout_user)
.or(edit_user)
.or(delete_user);
warp::serve(apis)
.run(([127, 0, 0, 1], 3030)) // 监听 127.0.0.1
.await;
}
const CREATE_METHOD: &'static str = "CREATE";
const LOGOUT_METHOD: &'static str = "LOGOUT";
#[derive(Debug)]
struct MethodError;
impl warp::reject::Reject for MethodError {}
fn method(name: &'static str) -> impl Filter<Extract = (), Error = warp::Rejection> + Clone {
warp::method()
.and_then(move |m: Method| async move {
if m == name {
Ok(())
} else {
Err(warp::reject::custom(MethodError))
}
})
.untuple_one()
}
async fn create_user() -> Result<impl warp::Reply, warp::Rejection> {
Ok("创建用户".to_string())
}
async fn login_user() -> Result<impl warp::Reply, warp::Rejection> {
Ok("用户登录".to_string())
}
async fn logout_user() -> Result<impl warp::Reply, warp::Rejection> {
Ok("用户退出".to_string())
}
async fn edit_user(id: u32) -> Result<impl warp::Reply, warp::Rejection> {
Ok(format!("修改用户{}信息", id))
}
async fn delete_user(id: u32) -> Result<impl warp::Reply, warp::Rejection> {
Ok(format!("注销用户{}信息", id))
}
使用postman进行测试的结果如下所示:
OK,到这里,我们非常简单的 RESTFul API已经设计完毕了。使用 warp 来办到这些是非常简单的,并且我们很容易就支持了自定义的 HTTP Method。