文章
Rust Web开发 - 2
使用Warp框架创建你的第一个API
部分内容超过 Notion API 单页读取上限,已尽力加载可访问内容。
看图2.1,注意框架所包含的技术栈。你将总会选择一个运行时和在HTTP服务上抽象的库。HTTP库Hyper已经在Warp上了,而运行时Tokio则必须我们单独添加到Cargo.toml上。
我们将创建一个用于问答的服务,用于获取关于公司产品、流程和代码库的信息。
1.认识Web框架Warp#
选择Warp作为Web框架的原因有如下四点:
- 它足够小巧,不碍事,而且被广泛使用,有一个活跃的社区积极维护着。
- 它是基于Tokio运行时的,这是目前Rust生态中的标准运行时。
- 它有一个活跃的Discard频道,Warp框架的创建者和其他使用者经常在上面解答用户的疑惑。
- 它在Github上得到活跃的开发和更新,并且有着很好的文档。
1.1 Warp包含哪些内容#
由图2.1可以简单的看出,Warp包含名为Hyper的crate,Hyper是Warp中对HTTP服务器,如果对Java web开发比较熟悉,可以理解为是Java中的Tomcat,只不过Hyper更加轻量级,只专注于Http协议的处理。 在上一章提到过,Rust中对所有异步事件的处理都需要一个运行时,所以单独的Warp是无法工作的,Warp还需要配合运行时使用,Tokio自然是首选。 每个Warp开发的web应用,至少包含Warp本身和Tokio这两个crate。
1.2 Warp的过滤器系统#
对于每个框架来说:
- 在指定端口(1024或者以上)启动一个监听服务。
- 提供一个路由函数处理HTTP请求,该请求与路由函数函数指定的路径,HTTP方法和参数相匹配才能被处理。 在Warp中,路由是过滤器的集合,这些过滤器链接在一起。每个请求都尝试去匹配你创建的过滤器,如果不能匹配则继续尝试下一个。下面代码展示了这个过程:
use warp::Filter;
#[tokio::main]
async fn main() {
let hello = warp::path("hello")
.map(|| "Hello, World!".to_string());
warp::serve(hello)
.run(([127, 0, 0, 1], 3030))
.await;
}
Warp监听本机的3030端口,有一个“hello”的过滤器,默认情况Warp监听GET请求,上面示例代码运行后,本地就会启动一个http的服务器,我们通过浏览器访问http://localhost:3030/hello,服务器就会返回给浏览器一句”hello world”

2.获取第一个JSON响应#
每次接到HTTP请求时框架都会通过若干步骤进行处理:
- 检查HTTP请求中的请求路径
- 检查HTTP请求方法(GET、PUT、POST等)
- 将请求转发到处理这个请求的路由函数(会先匹配URI和请求方法)
- 在请求到达路由函数之前也可能在各个中间件中传递,中间件可能在权限认证,添加token信息等 无论我们使用的是哪个框架,它们都有相同的设计原则。但是实际实现的方法以及调用方式和时间可能会有所不同。
2.1 创建第一个API#
每个Web应用程序开始都致力于接收一个HTTP的请求并返回响应,然后你可以修改和拓展这种简单的工作模式。 Warp的主要概念是过滤器,我们的探索从get过滤器开始,这个过滤器用来过滤HTTP请求中的GET请求。然后添加一个路径,用于过滤对HTTP主机URL后的参数的请求。
.....
// 省略
async fn get_question() -> Result<impl warp::Reply, warp::Rejection> {
let question = Question::new(
QuestionId::from_str("1").expect("No id provide"),
"First Question".to_string(),
"Content of question".to_string(),
Some(vec!("faq".to_string())),
);
Ok(warp::reply::json(
&question
))
}
#[tokio::main]
async fn main() {
let get_items = warp::get()
.and(warp::path("question"))
.and(warp::path::end())
.and_then(get_question);
let routes = get_items;
warp::serve(routes)
.run(([127, 0, 0, 1], 3030))
.await;
}
以上面代码为例:warp会过滤所有URL以http://localhost:3030/question 开头的GET请求,由于我们使用了.and(warp::path::end()) 过滤器,所以只会精准拦截/question(而不是/question/xxx/xx等这种URI)。最终所有的/question的GET请求都会去掉用and_then()中的函数,此例中是get_question() 函数。
路由函数必须有固定的返回格式。固定的格式如下:
- 结果
- warp::Reply(针对成功的部分)
- warp::Rejection(针对错误)
我们的示例中返回的是一个question的json格式数据,由于我们的Question对象没有实现序列化,所以这里报错了
warp::reply::json方法的是这样的:
它要求参数是一个引用,并且实现了序列化(Serialize)接口,我们点击上面的Serialize
会跳转到Overview · Serde。接下来我们需要实现实现序列化接口:
如果使用的是Rust Rover,它会自动在Cargo.toml中添加依赖:
然后cargo run 启动我们的warp应用
通过浏览访问http://localhost:3030/question,页面显示如下:
2.2 错误处理#
基本上我们可以理解为Warp中的设计哲学是:一切皆过滤器。如果存在一个请求的URL无法被过滤器处理,Warp就会返回一个404错误码给客户端。通常在Web应用的设计中我们会给自己的web应用设置一个统一的404页面或者统一的404的响应信息用来处理这类请求。 Warp为我们提供了一个过滤器用来应对这种需求,这个过滤器被叫做recover()过滤器,它可以被添加到多个过滤器组成的过滤链末端,使用的示例代码如下所示:
#[tokio::main]
async fn main() {
let get_items = warp::get()
.and(warp::path("question"))
.and(warp::path::end())
.and_then(get_question) // 注意这里传入的是一个函数名而不是一个函数调用,
.recover(return_error);
let routes = get_items;
warp::serve(routes)
.run(([127, 0, 0, 1], 3030))
.await;
}