从零开始使用 axum-server 构建 HTTP/HTTPS 服务
axum-server 是 Rust 生态中为 axum 框架设计的高性能服务器实现,基于 hyper(底层 HTTP 引擎)和 tower(服务抽象)构建,支持 HTTP/1、HTTP/2 及 HTTPS。本教程将从环境准备到实战功能,一步步带你掌握 axum-server 的使用。
1. 环境准备:安装 Rust 与工具链
首先需要搭建 Rust 开发环境,这是运行 axum-server 项目的基础。
步骤 1:安装 Rust
打开终端,执行官方安装脚本(适用于 Windows/macOS/Linux):
# 安装 Rust 工具链(包含 cargo、rustc 等)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
按照提示完成安装,最后执行以下命令让环境变量生效:
# Linux/macOS
source $HOME/.cargo/env
# Windows(PowerShell)
$env:Path += ";$HOME\.cargo\bin"
步骤 2:验证环境
执行以下命令,确认 Rust 与 Cargo 安装成功:
rustc --version # 应显示 Rust 版本(建议 1.70+)
cargo --version # 应显示 Cargo 版本
2. 第一个项目:Hello World 服务
我们从最简单的 HTTP 服务开始,实现 “访问指定地址返回 Hello World” 的功能。
步骤 1:创建新项目
打开终端,执行以下命令创建名为 axum-server-demo
的 Rust 项目:
cargo new axum-server-demo
cd axum-server-demo
步骤 2:添加依赖
修改项目根目录下的 Cargo.toml
文件,添加 axum
、axum-server
和 tokio
(异步运行时)的依赖:
[package]
name = "axum-server-demo"
version = "0.1.0"
edition = "2021"[dependencies]
# axum 框架:用于定义路由和处理请求
axum = "0.7"
# axum-server:核心服务器实现
axum-server = "0.7"
# tokio:异步运行时(axum/axum-server 依赖异步)
tokio = { version = "1.0", features = ["full"] }
# 用于处理网络地址(SocketAddr)
std-sys = "0.1" # 或直接使用 std 的 net 模块(无需额外依赖,本示例用 std)
步骤 3:编写核心代码
打开 src/main.rs
文件,替换为以下代码(关键步骤已加注释):
// 1. 导入所需模块
use axum::{routing::get, Router}; // axum 的路由与路由器
use axum_server::Server; // axum-server 的核心服务器类型
use std::net::SocketAddr; // 标准库的网络地址类型// 2. 异步主函数(axum/axum-server 基于异步,需用 tokio::main 宏)
#[tokio::main]
async fn main() {// 3. 定义路由:访问根路径(/)时,用 GET 方法触发 handler// handler 是一个异步函数,返回 "Hello, axum-server!"let app = Router::new().route("/", get(|| async { "Hello, axum-server!" }));// 4. 定义服务器监听地址:127.0.0.1(本地回环),端口 3000let addr = SocketAddr::from(([127, 0, 0, 1], 3000));println!("服务器已启动,监听地址:http://{}", addr);// 5. 创建并启动服务器// - bind(addr):绑定监听地址,生成 Server 实例// - serve(app.into_make_service()):将 axum 的 Router 转换为 tower 的 MakeService(axum-server 要求)// - await:异步等待服务器运行(阻塞主线程,直到服务器停止)// - unwrap():简化错误处理(生产环境需替换为 proper error handling)Server::bind(addr).serve(app.into_make_service()).await.unwrap();
}
步骤 4:运行与测试
(1)启动服务器:在项目根目录执行以下命令:
cargo run
终端会输出:服务器已启动,监听地址:http://127.0.0.1:3000
。
(2)测试服务:
- 方法 1:打开浏览器,访问
http://127.0.0.1:3000
,页面会显示Hello, axum-server!
。 - 方法 2:用终端执行
curl http://127.0.0.1:3000
,会返回同样的字符串。
3. 进阶功能 1:多路由与请求处理
实际项目中需要多个路由,我们扩展示例,添加 “获取用户信息”“处理 POST 请求” 的功能。
步骤 1:更新代码(支持多路由与 JSON)
修改 src/main.rs
,添加 JSON 处理依赖(需先在 Cargo.toml
中添加 serde
):
# 在 Cargo.toml 的 [dependencies] 中添加
serde = { version = "1.0", features = ["derive"] } # 用于 JSON 序列化/反序列化
axum::extract::Json = "0.7" # axum 内置的 JSON 提取器(无需额外依赖,已包含在 axum 中)
然后更新 src/main.rs
代码:
use axum::{extract::Json, // 提取 JSON 请求体routing::{get, post}, // 支持 GET 和 POST 方法Router,
};
use axum_server::Server;
use serde::Serialize; // 用于序列化响应
use std::net::SocketAddr;// 定义用户信息结构体(用于响应 JSON)
#[derive(Serialize)]
struct User {id: u32,name: String,email: String,
}// 定义 POST 请求体结构体(用于接收客户端数据)
#[derive(serde::Deserialize)]
struct CreateUserRequest {name: String,email: String,
}// 异步 handler:获取指定 ID 的用户信息(路径参数:id)
async fn get_user(id: axum::extract::Path<u32>) -> Json<User> {// 模拟从数据库获取用户(实际项目中替换为真实逻辑)let user = User {id: id.0, // 提取路径参数中的 IDname: "Alice".to_string(),email: "alice@example.com".to_string(),};Json(user) // 返回 JSON 格式的用户信息
}// 异步 handler:创建用户(接收 JSON 请求体)
async fn create_user(Json(req): Json<CreateUserRequest>) -> Json<User> {// 模拟创建用户(实际项目中需保存到数据库)let new_user = User {id: 100, // 模拟自动生成的 IDname: req.name,email: req.email,};Json(new_user) // 返回创建后的用户信息
}#[tokio::main]
async fn main() {// 定义多路由let app = Router::new().route("/", get(|| async { "Hello, axum-server!" })) // 根路径.route("/users/:id", get(get_user)) // 获取用户(路径参数 :id).route("/users", post(create_user)); // 创建用户(POST 请求)let addr = SocketAddr::from(([127, 0, 0, 1], 3000));println!("服务器已启动,监听地址:http://{}", addr);Server::bind(addr).serve(app.into_make_service()).await.unwrap();
}
步骤 2:测试多路由
(1)启动服务器:cargo run
。
(2)测试根路径:curl http://127.0.0.1:3000
→ 返回 Hello, axum-server!
。
(3)测试获取用户:curl http://127.0.0.1:3000/users/123
→ 返回 JSON:
{"id":123,"name":"Alice","email":"alice@example.com"}
(4)测试创建用户(POST 请求):
curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob","email":"bob@example.com"}' http://127.0.0.1:3000/users
返回 JSON(创建后的用户):
{"id":100,"name":"Bob","email":"bob@example.com"}
4. 进阶功能 2:启用 HTTPS(基于 rustls)
实际项目中需用 HTTPS 保证安全,axum-server 支持基于 rustls
的 HTTPS,我们来实现它。
步骤 1:准备 TLS 证书
首先需要生成本地测试证书(生产环境需从 CA 机构申请),推荐用 mkcert
工具:
安装 mkcert:
- macOS:
brew install mkcert
- Windows:
choco install mkcert
(需先安装 Chocolatey) - Linux:
sudo apt install libnss3-tools && curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" && chmod +x mkcert-v*-linux-amd64 && sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert
- macOS:
生成证书:
在项目根目录执行以下命令,生成localhost.pem
(证书)和localhost-key.pem
(私钥):
mkcert -install # 安装本地 CA(仅首次需要)
mkcert localhost 127.0.0.1 # 生成针对本地地址的证书
步骤 2:添加 HTTPS 依赖
修改 Cargo.toml
,添加 axum-server
的 tls-rustls
特性:
[dependencies]
# 其他依赖不变...
axum-server = { version = "0.7", features = ["tls-rustls"] } # 启用 rustls 支持
rustls-pemfile = "1.0" # 用于读取 PEM 格式的证书/私钥
步骤 3:修改代码启用 HTTPS
更新 src/main.rs
,替换服务器启动逻辑为 HTTPS 版本:
// 新增导入
use axum_server::tls_rustls::RustlsConfig;
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::fs::File;
use std::io::BufReader;// 其他代码(路由、handler)不变...#[tokio::main]
async fn main() {// 1. 读取 TLS 证书和私钥let cert_file = File::open("localhost.pem").unwrap(); // 证书文件路径let key_file = File::open("localhost-key.pem").unwrap(); // 私钥文件路径// 2. 解析证书(PEM 格式)let cert_chain = certs(&mut BufReader::new(cert_file)).unwrap().into_iter().map(|cert| rustls::Certificate(cert)).collect();// 3. 解析私钥(PKCS8 格式)let private_key = pkcs8_private_keys(&mut BufReader::new(key_file)).unwrap().into_iter().next().unwrap();let private_key = rustls::PrivateKey(private_key);// 4. 创建 Rustls 配置let tls_config = RustlsConfig::builder().with_single_cert(cert_chain, private_key).unwrap(); // 生产环境需处理错误// 5. 定义路由(与之前一致)let app = Router::new().route("/", get(|| async { "Hello, HTTPS!" })).route("/users/:id", get(get_user)).route("/users", post(create_user));let addr = SocketAddr::from(([127, 0, 0, 1], 3443)); // HTTPS 常用端口 443,测试用 3443println!("HTTPS 服务器已启动,监听地址:https://{}", addr);// 6. 启动 HTTPS 服务器(用 bind_rustls 替代 bind)axum_server::bind_rustls(addr, tls_config).serve(app.into_make_service()).await.unwrap();
}
步骤 4:测试 HTTPS 服务
- 启动服务器:
cargo run
→ 输出HTTPS 服务器已启动,监听地址:https://127.0.0.1:3443
。 - 测试 HTTPS 路径:
curl -k https://127.0.0.1:3443 # -k 忽略本地证书验证(测试用)
返回 Hello, HTTPS!
,表示 HTTPS 服务正常运行。
5. 进阶功能 3:服务器生命周期控制(优雅关闭)
在生产环境中,需要让服务器 “优雅关闭”(处理完现有请求后再停止,避免数据丢失),axum-server 提供 Handle
类型实现此功能。
步骤 1:更新代码(添加优雅关闭逻辑)
修改 src/main.rs
,关键新增 Handle
和信号监听:
// 新增导入:用于监听系统信号(如 Ctrl+C)
use axum_server::Handle;
use tokio::signal;
use tokio::sync::oneshot;// 其他代码(路由、handler、TLS 配置)不变...#[tokio::main]
async fn main() {// 1. 创建 Handle(用于控制服务器关闭)let handle = Handle::new();let shutdown_handle = handle.clone(); // 克隆用于信号监听任务// 2. 启动信号监听任务(独立于服务器,监听 Ctrl+C 或 SIGTERM)tokio::spawn(async move {// 监听系统中断信号(Ctrl+C)signal::ctrl_c().await.unwrap();println!("\n收到关闭信号,开始优雅关闭服务器...");// 触发服务器优雅关闭(等待现有请求处理完成)shutdown_handle.shutdown();});// 3. 定义路由和 TLS 配置(与之前一致)let app = Router::new().route("/", get(|| async { "Hello, 优雅关闭!" })).route("/users/:id", get(get_user)).route("/users", post(create_user));let addr = SocketAddr::from(([127, 0, 0, 1], 3443));let tls_config = RustlsConfig::builder() // 复用之前的 TLS 配置逻辑.with_single_cert(cert_chain, private_key).unwrap();// 4. 启动服务器时绑定 Handleaxum_server::bind_rustls(addr, tls_config).handle(handle) // 将 Handle 传递给服务器.serve(app.into_make_service()).await.unwrap();println!("服务器已完全关闭");
}
步骤 2:测试优雅关闭
(1)启动服务器:cargo run
。
(2)触发关闭:在终端按 Ctrl+C
,会看到:
收到关闭信号,开始优雅关闭服务器...
服务器已完全关闭
(3)验证效果:如果在按 Ctrl+C
前发起一个慢请求(如模拟耗时处理),服务器会等待请求完成后再关闭,而非强制中断。
6. 总结与进阶方向
通过本教程,你已掌握 axum-server 的核心用法:
- 搭建基础 HTTP 服务,实现多路由与 JSON 处理;
- 启用 HTTPS(基于 rustls);
- 实现服务器优雅关闭。
后续还可以增加以下内容:
- 错误处理:替换示例中的
unwrap()
,用thiserror
或anyhow
处理实际项目中的错误; - 生产配置:优化 TLS 配置(如启用 TLS 1.3、添加证书链)、调整服务器参数(如连接数限制);
- 扩展功能:结合 axum 的中间件(如日志、认证)、使用
axum-server
的from_tcp
从现有 TCP 监听创建服务器; - 性能优化:基于 hyper 的特性调整线程池、启用 HTTP/2 优先级等。