使用 Actix Web 构建 Web 应用

使用 Actix Web 构建 Web 应用

1. 添加依赖

首先,在 Cargo.toml 中添加 actix-web 和 tokio 依赖:

[dependencies]
actix-web = "4.11.0"
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }

说明:

  • actix-web 是 Rust 中高性能的异步 Web 框架。
  • tokio 提供异步运行时,rt-multi-thread 启用多线程执行器,macros 支持 #[tokio::main] 等宏。

2. 编写一个简单的 Web 服务

use actix_web::{App, HttpRequest, HttpServer, Responder, web};

async fn greet(req: HttpRequest) -> impl Responder {
    let name = req.match_info().get("name").unwrap_or("world");
    format!("Hello {}!", name)
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(greet))
            .route("/{name}", web::get().to(greet))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}

该程序启动一个监听 127.0.0.1:8000 的 HTTP 服务器:

  • 访问 http://127.0.0.1:8000/ 返回 Hello world!
  • 访问 http://127.0.0.1:8000/Alice 返回 Hello Alice!

3. Actix Web 应用程序核心组件解析

3.1 HttpServer:传输层的入口

HttpServer 负责处理底层网络通信,是整个应用的“门面”。其主要职责包括:

  • 监听地址配置: 绑定 TCP 地址(如 127.0.0.1:8000)或 Unix 域套接字;
  • 连接管理:控制最大并发连接数、连接速率等;
  • 传输安全: 支持通过 TLS(如 rustls 或 openssl)启用 HTTPS。

关键点: HttpServer 不处理业务逻辑,仅负责接收连接并将其交给上层应用。

3.2 App:应用逻辑的容器
当 HttpServer 接收到新连接后,请求会被交由 App 处理。App 是业务逻辑的核心载体,包含:

  • 路由(Routes):定义 URL 路径与处理函数的映射;
  • 中间件(Middleware): 用于日志、认证、错误处理等横切关注点;
  • 服务配置: 如状态共享、数据注入等。
    示例中的 App 配置如下:
App::new()
    .route("/", web::get().to(greet))
    .route("/{name}", web::get().to(greet))
  • App::new() 创建一个空的应用实例;
  • .route(path, handler) 采用链式调用方式注册多个端点。

3.3 路由(Route)与处理器(Handler)

路由的组成

每个 .route() 调用包含两个要素:

  • 路径(Path)
    • 静态路径:如 “/”
    • 动态模板:如 “/{name}”,其中 {name} 是路径参数。
  • 路由守卫(Guard) + 处理器(Handler)
    • web::get()Route::new().guard(guard::Get()) 的简写,表示仅匹配 GET 请求
    • 守卫(Guard)是一组条件(如 HTTP 方法、Header 等),只有全部满足时才会调用处理器。

匹配机制:

当请求到达时,Actix Web 会按注册顺序遍历所有路由,第一个同时满足路径模板和守卫条件的路由将被选中,并调用其处理器。

处理器函数签名

处理器是一个异步函数,其典型签名如下:

async fn greet(req: HttpRequest) -> impl Responder {
    let name = req.match_info().get("name").unwrap_or("world");
    format!("Hello {}!", name)
}

扩展性:你可以为自定义类型实现 Responder,从而直接返回结构化响应(如 JSON 对象)

4. #[tokio::main] 的作用详解

在 Rust 中,#[tokio::main] 是一个非常常见但又容易被忽略的过程宏。它看似只是让 main 函数能使用 async,但背后其实做了很多底层工作。

4.1 为什么需要异步 main 函数?

我们通常希望在异步环境中运行程序的入口函数,例如:

#[tokio::main]
async fn main() {
    HttpServer::new(|| App::new().route("/", web::get().to(greet)))
        .bind("127.0.0.1:8000")
        .unwrap()
        .run()
        .await
        .unwrap();
}

这里的 HttpServer::run() 是一个 异步方法,而在 Rust 中,只能在异步函数中调用异步函数。
这就意味着 main 也必须是异步的。

但问题是:

Rust 的 main 函数不能直接是异步的。

为什么?这要从 Rust 的异步机制说起。

4.2 Rust 异步编程的底层原理

Rust 的异步编程基于 Future trait。
Future 代表一个可能尚未完成的计算,它通过一个 poll 方法逐步推进执行。

换句话说,Rust 的 Future 是惰性的:
除非有外部“驱动者”调用 poll,它不会自动执行。这种机制称为 “被动型(pull)” 异步模型。

Rust 本身虽然支持 async 语法,但并没有内置异步运行时(runtime)。
这意味着:

  • 编译器能帮你生成 Future

  • 但不会帮你驱动它执行。

4.3 谁来执行 Future

既然 Rust 没有内置运行时,那就需要你在项目中引入一个异步运行时(runtime)库,比如:

[dependencies]
tokio = { version = "1", features = ["full"] }

Tokio 就是最常用的异步运行时之一。它负责:

  • 启动多线程调度器;

  • 管理任务执行;

  • 驱动 Futurepoll

  • 提供异步 IO、定时器等功能。

4.4 main 为何不能直接是异步的?

main 函数是程序的入口点,操作系统只会调用一个普通的同步函数 fn main().
而异步函数返回的是一个 Future 对象,不会自动运行。

因此编译器并不知道:
“谁应该来执行这个 Future(也就是谁负责调用 poll)?”

Rust 没有规定运行时标准接口,所以这就需要我们自己来启动一个运行时,然后在其中运行异步任务。

4.5 #[tokio::main] 到底做了什么?

你可能已经猜到了:
#[tokio::main] 的作用就是——
在 main 函数中自动创建并启动一个 Tokio 运行时,然后在其中执行你的异步逻辑。

为了验证这一点,我们可以使用工具 cargo-expand 展开宏:

cargo install cargo-expand
cargo expand

运行后你会看到类似的展开结果:

fn main() -> std::io::Result<()> {
    let body = async {
        HttpServer::new(|| {
            App::new()
                .route("/", web::get().to(greet))
                .route("/{name}", web::get().to(greet))
        })
        .bind("127.0.0.1:8000")?
        .run()
        .await
    };

    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .expect("Failed building the Runtime")
        .block_on(body)
}

可以看到:

  • 展开后 main 变成了一个普通同步函数;

  • 宏自动生成了一个 Tokio 运行时;

  • block_on(body) 来驱动异步代码执行。

这就解释了为什么 #[tokio::main] 能“让异步的 main 函数编译通过”——
其实它只是帮你包了一层运行时初始化代码。

5. 实现健康检查处理器(Health Check Handler)

在 Web 服务中,健康检查(health check) 通常用于验证服务器是否处于可用状态。
当客户端向 /health_check 发送 GET 请求时,服务器应返回 状态码 200,且无响应体。

5.1 编写健康检查处理器

我们首先创建一个异步请求处理函数。参考之前的 greet 函数,先定义如下:

async fn health_check(req: HttpRequest) -> impl Responder {
    todo!()
}

这里的 req 表示传入的 HTTP 请求对象。
不过在健康检查中,我们并不需要使用任何请求信息,因此该参数其实是多余的。

5.2 构建响应对象

由于 HttpResponse 类型实现了 Responder trait,我们可以直接返回一个 HttpResponse。

HttpResponse::Ok() 会返回一个以 200 OK 为状态码的 HttpResponseBuilder
我们可以调用 .finish() 来构建一个无响应体的 HttpResponse

async fn health_check() -> impl Responder {
    HttpResponse::Ok().finish()
}

说明
HttpResponseBuilder 本身也实现了 Responder trait
因此即使不调用 .finish() 也能正常工作。
不过,为了语义更明确、风格一致,推荐保留 .finish()

5.3 注册路由

接下来,我们在应用中将 /health_check 路由与刚定义的处理器绑定:

App::new()
    .route("/health_check", web::get().to(health_check))

这样,当客户端访问 /health_check 时,服务器就会返回:

HTTP/1.1 200 OK
Content-Length: 0

5.4 最终代码示例

use actix_web::{web, App, HttpResponse, HttpServer, Responder};

async fn health_check() -> impl Responder {
    HttpResponse::Ok().finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/health_check", web::get().to(health_check))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}

运行后,访问 http://127.0.0.1:8000/health_check即可看到一个返回 200 OK 且无响应体的健康检查接口
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值