从零实现一个 HTTP 服务器
使用 Tokio + Hyper 构建一个支持路由、中间件、静态文件服务的异步 HTTP 服务器
你将学到
- 使用 Tokio 构建异步运行时
- 使用 Hyper 处理 HTTP 请求和响应
- 实现路由系统
- 添加中间件支持
前置知识
架构设计
http-server/
├── src/
│ ├── main.rs # 入口
│ ├── server.rs # 服务器核心
│ ├── router.rs # 路由系统
│ ├── handler.rs # 请求处理
│ └── middleware.rs # 中间件
└── Cargo.toml
实现步骤
第一步:项目初始化
cargo new http-server
cd http-server
# Cargo.toml
[package]
name = "http-server"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["tokio"] }
http-body-util = "0.1"
bytes = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
第二步:路由系统
// src/router.rs
use hyper::{Method, Request, Response, StatusCode};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
type Handler = Box<dyn Fn(Request<hyper::body::Incoming>) -> Pin<Box<dyn Future<Output = Response<String>> + Send>> + Send + Sync>;
pub struct Router {
routes: HashMap<(Method, String), Handler>,
}
impl Router {
pub fn new() -> Self {
Router {
routes: HashMap::new(),
}
}
pub fn add<F, Fut>(&mut self, method: Method, path: &str, handler: F)
where
F: Fn(Request<hyper::body::Incoming>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Response<String>> + Send + 'static,
{
let path = path.to_string();
self.routes.insert(
(method, path),
Box::new(move |req| Box::pin(handler(req))),
);
}
pub async fn handle(&self, req: Request<hyper::body::Incoming>) -> Response<String> {
let method = req.method().clone();
let path = req.uri().path().to_string();
if let Some(handler) = self.routes.get(&(method, path)) {
handler(req).await
} else {
Response::builder()
.status(StatusCode::NOT_FOUND)
.body("404 Not Found".to_string())
.unwrap()
}
}
}
第三步:请求处理
// src/handler.rs
use hyper::{Request, Response, StatusCode};
use serde_json::json;
pub async fn index(_req: Request<hyper::body::Incoming>) -> Response<String> {
Response::new("Hello, World!".to_string())
}
pub async fn api_users(_req: Request<hyper::body::Incoming>) -> Response<String> {
let users = json!({
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
]
});
Response::builder()
.header("Content-Type", "application/json")
.body(users.to_string())
.unwrap()
}
pub async fn not_found(_req: Request<hyper::body::Incoming>) -> Response<String> {
Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Page not found".to_string())
.unwrap()
}
第四步:服务器核心
// src/server.rs
use hyper::server::conn::http1;
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use std::sync::Arc;
use crate::router::Router;
pub struct Server {
router: Arc<Router>,
addr: String,
}
impl Server {
pub fn new(router: Router, addr: &str) -> Self {
Server {
router: Arc::new(router),
addr: addr.to_string(),
}
}
pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let listener = TcpListener::bind(&self.addr).await?;
println!("Server running on http://{}", self.addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let router = self.router.clone();
tokio::spawn(async move {
let service = hyper::service::service_fn(move |req| {
let router = router.clone();
async move { Ok::<_, hyper::Error>(router.handle(req).await) }
});
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
eprintln!("Error serving connection: {}", err);
}
});
}
}
}
第五步:主程序
// src/main.rs
mod server;
mod router;
mod handler;
use hyper::Method;
use router::Router;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut router = Router::new();
// 注册路由
router.add(Method::GET, "/", handler::index);
router.add(Method::GET, "/api/users", handler::api_users);
// 启动服务器
let server = server::Server::new(router, "127.0.0.1:3000");
server.run().await?;
Ok(())
}
完整项目结构
http-server/
├── src/
│ ├── main.rs # 程序入口
│ ├── server.rs # TCP 监听和连接处理
│ ├── router.rs # 路由匹配和分发
│ ├── handler.rs # 具体请求处理函数
│ └── middleware.rs # 中间件(日志/CORS等)
└── Cargo.toml
最佳实践
- 使用 Arc 共享路由:多个连接需要共享路由表
- 异步处理每个连接:使用 tokio::spawn 并发处理
- 错误处理:使用 Result 传播错误
- 中间件模式:在路由前添加通用逻辑
常见问题
Q: 如何处理 POST 请求体?
A: 使用 hyper::body::to_bytes 读取请求体。
Q: 如何添加 CORS 支持? A: 在响应中添加 CORS 头。
Q: 如何支持 HTTPS?
A: 使用 tokio-rustls 或 hyper-tls。
扩展挑战
- 添加静态文件服务
- 实现 WebSocket 支持
- 添加请求日志中间件
相关课程
| 课程 | 相关章节 |
|---|---|
| Rust | 异步编程:async/await |
| Rust | Tokio 运行时基础 |
| Rust | 结构体(Struct) |
| Rust | 错误处理:Result 与 Option |