返回文章列表

文章

Rust Web开发 - 2

使用Warp框架创建你的第一个API

部分内容超过 Notion API 单页读取上限,已尽力加载可访问内容。

目录
  1. 1.认识Web框架Warp
  2. 1.1 Warp包含哪些内容
  3. 1.2 Warp的过滤器系统
  4. 2.获取第一个JSON响应
  5. 2.1 创建第一个API
  6. 2.2 错误处理

看图2.1,注意框架所包含的技术栈。你将总会选择一个运行时和在HTTP服务上抽象的库。HTTP库Hyper已经在Warp上了,而运行时Tokio则必须我们单独添加到Cargo.toml上。                                         图2.1 我们将创建一个用于问答的服务,用于获取关于公司产品、流程和代码库的信息。

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的过滤器系统#

对于每个框架来说:

  1. 在指定端口(1024或者以上)启动一个监听服务。
  2. 提供一个路由函数处理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请求时框架都会通过若干步骤进行处理:

  1. 检查HTTP请求中的请求路径
  2. 检查HTTP请求方法(GET、PUT、POST等)
  3. 将请求转发到处理这个请求的路由函数(会先匹配URI和请求方法)
  4. 在请求到达路由函数之前也可能在各个中间件中传递,中间件可能在权限认证,添加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;
}