Rust 框架 Tower:整体架构与日志记录中间件示例

604 阅读6分钟

概述

Rust 是一种系统编程语言,以其安全性、并发性和性能而著称。在 Rust 生态中,Tower 是一个非常流行的异步服务框架,它提供了一种高效的方式来构建和组合异步服务。Tower 的设计哲学是将复杂的异步服务分解为更小、更易于管理的组件,并通过中间件来增强这些组件的功能。

架构

Tower 的核心架构基于几个关键概念:

  1. 服务(Service):服务是 Tower 的基本单元,它定义了异步操作的输入和输出。服务可以被看作是一个异步函数,接受请求并返回响应。
  2. 堆叠(Stack):堆叠是服务的组合方式,通过将多个服务层叠在一起,可以逐步构建复杂的服务逻辑。
  3. 中间件(Middleware):中间件是服务堆叠中的一部分,用于在服务调用前后执行额外的逻辑,如日志记录、认证、缓存等。

运行机制

Tower 的运行机制主要依赖于异步任务和事件循环。每个服务都是一个异步任务,当请求到达时,请求会被传递到服务堆叠的顶部,然后逐层向下传递,直到达到底部的服务。响应则反向传递回客户端。

  1. 请求处理:当一个请求到达时,它会被传递到服务堆叠的顶部。每一层中间件都会处理这个请求,并决定是继续传递到下一层,还是直接返回响应。
  2. 响应处理:响应从底部服务开始,逐层向上传递,每一层中间件可以修改响应或添加额外的信息。

日志记录中间件示例

接下来,我们将通过一个简单的日志记录中间件示例,详细介绍如何编写中间件和服务。我们将使用 Rust 的标准日志库 logenv_logger 来记录日志。

添加依赖

首先,在你的 Cargo.toml 文件中添加以下依赖:

[dependencies]
tower = "0.4"
futures = "0.3"
log = "0.4"
env_logger = "0.9"

编写服务

创建一个简单的服务,接收整数并返回其二倍值。

use tower::Service;
use futures::future::{self, BoxFuture};

// MyService 结构体代表一个服务。
struct MyService;

// 为 MyService 实现 Service trait,使其能够处理 u32 类型的请求。
impl Service<u32> for MyService {
    // 定义服务处理请求后返回的响应类型。
    type Response = u32;
    // 定义服务可能返回的错误类型。
    type Error = std::convert::Infallible;
    // 定义服务返回的 Future 类型,这里使用 BoxFuture 允许返回异步操作。
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    // poll_ready 检查服务是否准备好接收请求。
    fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
        std::task::Poll::Ready(Ok(()))
    }

    // call 方法接收一个请求,并返回一个 Future,该 Future 将产生服务的响应。
    fn call(&mut self, req: u32) -> Self::Future {
        future::ready(Ok(req * 2)).boxed()
    }
}

注释说明:

  • Service<u32>:表示这个服务接受 u32 类型的请求,并返回 u32 类型的响应。
  • type Response = u32;:定义服务的响应类型为 u32
  • type Error = std::convert::Infallible;:定义服务的错误类型。这里使用 Infallible 表示服务不会发生错误。
  • type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;:定义服务返回的 Future 类型。这里使用 BoxFuture 允许返回的 Future 被动态分派。
  • poll_ready:检查服务是否准备好接收请求。这里直接返回 Poll::Ready(Ok(())) 表示服务始终准备好。
  • call:接收一个请求并返回一个响应。这里通过 future::ready 立即返回响应。

编写日志记录中间件

创建一个中间件,记录请求和响应。

use tower::Service;
use futures::future::{self, BoxFuture};

// LogMiddleware 结构体包装另一个服务 S。
struct LogMiddleware<S> {
    inner: S,
}

// 为 LogMiddleware 实现 Service trait,使其能够处理请求。
impl<S> Service<u32> for LogMiddleware<S>
where
    S: Service<u32, Response = u32>,
{
    // 响应和错误类型与内部服务 S 相同。
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    // poll_ready 检查中间件和内部服务是否准备好接收请求。
    fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
        println!("Middleware is ready to handle requests");
        self.inner.poll_ready(cx)
    }

    // call 方法接收一个请求,记录日志,然后调用内部服务。
    fn call(&mut self, req: u32) -> Self::Future {
        println!("Middleware received request: {}", req);
        // 调用内部服务处理请求。
        let inner_future = self.inner.call(req);
        // 使用 async block 来等待内部服务的响应。
        async move {
            let response = inner_future.await?;
            println!("Middleware received response: {}", response);
            Ok(response)
        }
    }
}

注释说明:

  • struct LogMiddleware<S>:定义一个中间件结构体,其中包含一个 inner 字段,它是被包裹的服务。
  • impl<S> Service<u32> for LogMiddleware<S>:为 LogMiddleware 实现 Service trait。
  • where S: Service<u32, Response = u32>:约束 S 必须实现 Service<u32> 且其响应类型为 u32
  • fn poll_ready(&mut self, cx: &mut std::task::Context<'_>):检查中间件是否准备好接收请求。这里调用内部服务的 poll_ready 方法,并打印日志。
  • fn call(&mut self, req: u32) -> Self::Future:接收一个请求并调用内部服务。这里在请求前后打印日志,并返回内部服务的响应。

运行示例

创建服务和中间件的实例,并发送请求。

fn main() {
    // 初始化日志系统。
    env_logger::init();

    // 创建 MyService 的实例。
    let service = MyService;
    // 创建 LogMiddleware 的实例,并将 MyService 作为内部服务。
    let middleware = LogMiddleware { inner: service };

    // 使用 oneshot 方法发送请求并获取响应。
    let response = middleware.oneshot(2).unwrap();
    println!("Response: {}", response);
}

注释说明:

  • env_logger::init();:初始化日志记录系统,以便记录日志。
  • let service = MyService;:创建一个 MyService 实例。
  • let middleware = LogMiddleware { inner: service };:创建一个 LogMiddleware 实例,并将 MyService 实例作为内部服务。
  • let response = middleware.oneshot(2).unwrap();:发送一个请求并获取响应。这里使用 oneshot 方法发送单个请求并等待响应。

Tower 常用 API 介绍

  • Service Trait:这是定义服务的基本 trait。任何想要成为服务的类型都必须实现这个 trait。它要求实现 poll_readycall 方法。
    • poll_ready:这个方法用于检查服务是否准备好接收请求。它返回一个 Poll,表示服务是否已经准备好。
    • call:这个方法接受一个请求,并返回一个异步的 Future,该 Future 将在完成时提供响应。
  • BoxFuture:这是 futures crate 中的一个类型,表示一个异步的、可发送的 Future。在 Service trait 中,Future 类型被定义为 BoxFuture,以允许返回的 Future 被动态分派。
  • ServiceExt Trait:这是 Tower 提供的一个扩展 trait,它为 Service 提供了一些额外的方法,如 oneshot,用于发送单个请求并等待响应。

资源链接

为了进一步学习和深入理解 Tower 框架,以下是一些有用的资源:

通过以上介绍,你应该对 Tower 框架有了基本的了解,并能够开始尝试使用它来构建自己的异步服务。希望这些示例和建议对你有所帮助!