1. 源代码
不多说,直接贴代码:
Cargo.toml
toml
[package]
name = "restfultest"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
warp = "0.3"
tokio = { version = "1", features = ["full"] }
crossbeam = "0.8.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
rand = "0.8.5"
lazy_static = "1.4"
rust
use lazy_static::lazy_static;
use std::collections::HashMap;
use warp::Filter;
use std::sync::Mutex;
// 模拟数据库
lazy_static! {
static ref DB: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
}
#[tokio::main]
async fn main() {
// 定义路由
let routes = warp::get()
.and(warp::path!("items" / String))
.and_then(get_item)
.or(warp::post()
.and(warp::path("items"))
.and(warp::body::json())
.and_then(create_item))
.or(warp::put()
.and(warp::path!("items" / String))
.and(warp::body::json())
.and_then(update_item))
.or(warp::delete()
.and(warp::path!("items" / String))
.and_then(delete_item));
// 启动服务器
warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
}
// 处理 GET 请求,获取指定项目
async fn get_item(id: String) -> Result<impl warp::Reply, warp::Rejection> {
let db = DB.lock().unwrap();
match db.get(&id) {
Some(item) => Ok(warp::reply::json(&item)),
None => Err(warp::reject::not_found()),
}
}
// 处理 POST 请求,创建新项目
async fn create_item(item: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
let mut db = DB.lock().unwrap();
let id = item.get("id").unwrap_or(&String::new()).to_string();
db.insert(id, item.get("value").unwrap_or(&String::new()).to_string());
Ok(warp::reply::json(&item))
}
// 处理 PUT 请求,更新指定项目
async fn update_item(
id: String,
item: HashMap<String, String>,
) -> Result<impl warp::Reply, warp::Rejection> {
let mut db = DB.lock().unwrap();
if db.contains_key(&id) {
db.insert(id, item.get("value").unwrap_or(&String::new()).to_string());
Ok(warp::reply::json(&item))
} else {
Err(warp::reject::not_found())
}
}
// 处理 DELETE 请求,删除指定项目
async fn delete_item(id: String) -> Result<impl warp::Reply, warp::Rejection> {
let mut db = DB.lock().unwrap();
if db.remove(&id).is_some() {
Ok(warp::reply())
} else {
Err(warp::reject::not_found())
}
}
2. 过滤器的工作原理
在Warp框架中,routes
变量是通过组合多个过滤器来定义的,这些过滤器共同描述了如何匹配和处理HTTP请求。下面详细解释routes
是如何接收参数,并把参数传递给子过滤器的,以及and
和or
运算在Warp过滤器中的作用。
参数传递
Warp的过滤器之间传递参数主要是通过组合和链式调用的方式。当一个请求到达Warp服务器时,它会按照定义的过滤器链进行匹配和处理。在这个过程中,每个过滤器都可以访问和修改请求数据,也可以产生一些中间结果,这些结果可以被链中的后续过滤器使用。
例如,在warp::path!("items" / String)
中,String
是一个捕获参数,当请求的路径匹配items/<some_string>
模式时,<some_string>
部分会被捕获并作为一个String
类型的值传递给后续的过滤器或处理函数。
and 运算
在Warp中,and
运算用于串联过滤器,确保请求必须依次通过所有串联的过滤器。每个and
后面的过滤器都会接收到前面过滤器产生的结果,并且可以基于这些结果进行进一步的处理或决策。
例如,在warp::get().and(warp::path!("items" / String))
中,warp::get()
过滤器首先检查请求方法是否为GET,如果是,则继续将请求传递给warp::path!("items" / String)
过滤器进行路径匹配。如果路径也匹配成功,那么捕获到的路径参数会传递给后续的.and_then(get_item)
处理函数。
or 运算
or
运算在Warp中用于提供备选的路由处理逻辑。当or
前面的过滤器链匹配失败时,Warp会尝试匹配or
后面的过滤器链。这允许你定义多个不同的路由处理逻辑,并根据请求的具体情况选择合适的逻辑来处理。
在你的代码中,通过连续使用.or
运算,你定义了一个能够处理GET、POST、PUT和DELETE请求的路由逻辑。当Warp接收到一个请求时,它会首先尝试匹配GET请求和特定路径的过滤器链,如果匹配失败,则会尝试匹配POST请求的过滤器链,以此类推。
综上所述,routes
变量通过组合多个过滤器来定义了一个复杂的路由处理逻辑,其中and
运算用于串联过滤器并确保请求必须满足所有条件,而or
运算则提供了备选的路由处理逻辑。
3. 关键代码分析
我们开发一下函数 and() 的实现:
rust
fn and<F>(self, other: F) -> And<Self, F>
where
Self: Sized,
<Self::Extract as Tuple>::HList: Combine<<F::Extract as Tuple>::HList>,
F: Filter + Clone,
F::Error: CombineRejection<Self::Error>,
{
And {
first: self,
second: other,
}
}
继续看 And 这个数据类型的定义:
rust
#[derive(Clone, Copy, Debug)]
pub struct And<T, U> {
pub(super) first: T,
pub(super) second: U,
}
impl<T, U> FilterBase for And<T, U>
where
T: Filter,
T::Extract: Send,
U: Filter + Clone + Send,
<T::Extract as Tuple>::HList: Combine<<U::Extract as Tuple>::HList> + Send,
CombinedTuples<T::Extract, U::Extract>: Send,
U::Error: CombineRejection<T::Error>,
{
type Extract = CombinedTuples<T::Extract, U::Extract>;
type Error = <U::Error as CombineRejection<T::Error>>::One;
type Future = AndFuture<T, U>;
fn filter(&self, _: Internal) -> Self::Future {
AndFuture {
state: State::First(self.first.filter(Internal), self.second.clone()),
}
}
}
...