开始前请先选择路线! 两条路线目标不同,不建议混用。
| 维度 | 路线 A:手写 HTTP | 路线 B:axum 框架 |
|---|---|---|
| 适合人群 | 想深入理解网络编程底层原理 | 想快速构建生产级服务 |
| 核心技术 | mio + 手写事件循环 + 状态机解析 | axum + tokio + tower |
| 对应 C++ 组件 | EventLoop, EPollPoller, Channel, Buffer | 全部由框架内部处理 |
| 代码量 | ~1500-2500 行 | ~200-400 行 |
| 开发时间 | 17-24 小时 | 4-6 小时 |
| 学习价值 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 生产就绪 | 需大量测试 | ✅ |
推荐路径:先完成路线 A 学习底层原理,再迁移到路线 B 用于生产项目。
┌──────────────────────────────────────────────────────────────┐
│ Main Reactor (1 个线程) │
│ EventLoop → EPollPoller → Acceptor │
│ ├── epoll_wait → accept() 新连接 │
│ ├── idleFd 优雅处理 fd 耗尽 │
│ └── Round-Robin 分发给 Sub Reactor │
├──────────────────────────────────────────────────────────────┤
│ Sub Reactor (N 个线程,如 4 个) │
│ EventLoop → EPollPoller → TcpConnection │
│ ├── 非阻塞 I/O,处理读写事件 │
│ ├── Buffer (readv 优化读取, 高水位保护) │
│ ├── TimerQueue (连接超时, Keep-Alive 管理) │
│ └── 连接状态机 (Connecting → Connected → Disconnecting) │
├──────────────────────────────────────────────────────────────┤
│ HTTP 协议层 │
│ HttpParser (状态机逐字节解析) │
│ HttpRequest (query/cookie/form 解析) │
│ HttpResponse (chunked 传输编码, 40+ 状态码) │
│ Router (静态路由 / 动态路由 / 通配符路由) │
└──────────────────────────────────────────────────────────────┘
| 功能 | 说明 |
|---|---|
| HTTP/1.0 & 1.1 | 状态机逐字节解析 |
| 7 种 HTTP 方法 | GET / POST / PUT / DELETE / HEAD / OPTIONS / PATCH |
| 动态路由 | /user/:id 匹配 /user/123,提取参数 |
| 通配符路由 | /static/*filepath 匹配 /static/css/style.css |
| 查询参数 | ?key=value 自动解析 |
| Cookie 解析 | parseCookie() |
| 表单解析 | parseForm() (application/x-www-form-urlencoded) |
| Keep-Alive | 长连接复用 + 超时管理 |
| Chunked 传输 | 请求和响应均支持 |
| Buffer 设计 | prependable + readable + writable 三段式,readv 优化 |
| 连接状态机 | 4 状态生命周期管理 |
| 高水位保护 | 防止发送缓冲区无限增长 |
| 定时器 | 基于 timerfd,支持 runAt/runAfter/runEvery |
| fd 耗尽处理 | Acceptor idleFd 优雅降级 |
| 内存池 | MemoryPool 加速小对象分配 |
| 概念 | C++ | Rust | 说明 |
|---|---|---|---|
| 构建系统 | CMake | Cargo (内置) | |
| 包管理 | 手动/vcpkg | Cargo (内置) | |
| RAII | 构造/析构函数 | Drop trait | Rust 保证无泄漏 |
| 移动语义 | std::move | 所有权系统 (默认移动) | 编译期检查 |
| 错误处理 | 异常/errno | Result<T, E> + ? | 编译期强制处理 |
| 空值 | nullptr | Option<T> | 模式匹配,杜绝空指针 |
| 线程安全 | std::mutex + 约定 | Send + Sync 编译期检查 | Rust 核心优势 |
| 状态机 | int 枚举 + if-else | enum + match 穷举 | Rust 巨大优势 |
| 智能指针 | unique_ptr/shared_ptr | Box<T>/Arc<T> | |
| I/O 多路复用 | epoll (手写) | mio 或 tokio (内置) |
[dependencies]
# 事件驱动 I/O (替代手写 epoll)
mio = { version = "1", features = ["os-poll", "os-ext", "net"] }
# 高性能 HTTP 解析 (可选,也可手写状态机)
httparse = "1"
# 字节缓冲 (零拷贝)
bytes = "1"
# 工具库
anyhow = "1"
thiserror = "2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.8"
tower = "0.5"
tower-http = { version = "0.6", features = ["fs", "trace"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1"
thiserror = "2"
mime_guess = "2"
阶段 1: 环境准备 (30 分钟)
├─ 安装 Rust (rustup)
├─ 创建 cargo 项目
└─ 添加依赖
阶段 2: 底层模块迁移 (3-4 小时)
├─ 错误类型定义 (error.rs)
├─ Socket RAII 封装 (socket.rs)
├─ HTTP 请求解析 (request.rs) + 单元测试
│ ├─ 请求行解析
│ ├─ 请求头解析
│ └─ Content-Length body 解析
└─ HTTP 响应构建 (response.rs) + 单元测试
阶段 3: 路由系统 (3-4 小时)
├─ 静态路由
├─ 动态路由 (:param 参数提取)
├─ 通配符路由 (*filepath 匹配)
└─ HTTP 方法路由 (GET/POST/PUT/DELETE)
阶段 4: 服务器整合 (3-4 小时)
├─ TcpListener 接受连接
├─ tokio::spawn 并发处理
├─ Keep-Alive 循环读写
├─ 请求大小限制
└─ 错误处理中间件
阶段 5: 进阶特性 (2-3 小时)
├─ 查询参数解析
├─ Cookie 解析
├─ 连接状态机 (enum + match)
├─ 超时管理
└─ 静态文件服务
阶段 6: 测试与优化 (2-3 小时)
├─ 单元测试 + 集成测试
├─ curl 端到端验证
├─ 性能压力测试 (wrk)
└─ Release 构建优化
| 原则 | 说明 |
|---|---|
| 不要一次全部重写 | 先让新项目能跑起来,再逐步替换模块 |
| 保留原测试用例 | 用相同输入验证输出一致性 |
| 先功能后性能 | 先保证逻辑正确,再优化 |
| 边迁移边写测试 | 每个模块迁移完立即写测试 |
| Git 频繁提交 | 每个阶段完成后 commit |
http_server_rust/
├── Cargo.toml
├── www/
│ └── index.html # 从原项目复制
├── src/
│ ├── main.rs # 入口:启动服务器 (Layer 4)
│ ├── lib.rs # 库根:模块声明与导出
│ ├── infra/ # Layer 1: 基础设施层
│ │ ├── mod.rs # 模块声明
│ │ ├── error.rs # 错误类型 (替代异常)
│ │ └── socket.rs # 原始 socket 封装 (学习 RAII)
│ ├── http/ # Layer 2: HTTP 协议层
│ │ ├── mod.rs # 模块声明
│ │ ├── request.rs # HTTP 请求解析 (含 Content-Length body)
│ │ ├── response.rs # HTTP 响应构建
│ │ ├── router.rs # 路由分发 (静态/动态/通配符)
│ │ └── connection.rs # 连接状态机 (学习 enum 状态机)
│ └── server/ # Layer 3: 服务器层
│ ├── mod.rs # 模块声明
│ └── http_server.rs # HTTP 服务器主循环
└── tests/
├── integration_test.rs
├── request_test.rs
└── router_test.rs
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
/// 自定义错误类型 — 对标 C++ 的异常处理
///
/// Rust 没有异常,所有错误通过 Result<T, E> 返回,
/// 编译期强制处理所有错误路径,不会遗漏。
#[derive(Debug, thiserror::Error)]
pub enum ServerError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("HTTP parse error: {0}")]
HttpParse(String),
#[error("Route not found: {0}")]
NotFound(String),
#[error("Method not allowed: {0}")]
MethodNotAllowed(String),
#[error("Internal server error: {0}")]
Internal(String),
}
// 让 ServerError 能直接作为 HTTP 响应返回
impl IntoResponse for ServerError {
fn into_response(self) -> Response {
let (status, message) = match &self {
ServerError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
ServerError::MethodNotAllowed(_) => (
StatusCode::METHOD_NOT_ALLOWED,
self.to_string(),
),
_ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
};
(status, message).into_response()
}
}
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
/// RAII Socket — 对标 C++ 的 net::Socket
///
/// 展示 Rust 如何用 Drop trait 实现 RAII。
/// OwnedFd 在析构时自动调用 close(fd),无需手动管理。
///
/// 注意:实际项目中应直接使用 tokio::net::TcpListener,
/// 此模块仅用于学习 unsafe FFI 和 RAII 模式。
pub struct Socket {
fd: OwnedFd,
}
impl Socket {
pub fn new() -> Result<Self, std::io::Error> {
let fd = unsafe {
libc::socket(
libc::AF_INET,
libc::SOCK_STREAM | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC,
0,
)
};
if fd < 0 {
return Err(std::io::Error::last_os_error());
}
// SAFETY: libc::socket 返回的 fd 是有效的,由 OwnedFd 管理生命周期
Ok(Self {
fd: unsafe { OwnedFd::from_raw_fd(fd) },
})
}
pub fn fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
// Drop 由 OwnedFd 自动实现,离开作用域时 close(fd)
}
use std::collections::HashMap;
use crate::error::ServerError;
/// HTTP 请求结构体 — 对标 C++ 的 HttpRequest
#[derive(Debug)]
pub struct HttpRequest {
pub method: String,
pub path: String,
pub version: String,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
}
impl HttpRequest {
/// 从原始字节流解析 HTTP 请求
///
/// 解析流程 (状态机):
/// 1. 解析请求行: "GET /path HTTP/1.1"
/// 2. 解析请求头: "Host: localhost:8080"
/// 3. 解析空行 (头部结束标志)
/// 4. 根据 Content-Length 读取 body
pub fn parse(raw: &[u8]) -> Result<Self, ServerError> {
let text = String::from_utf8_lossy(raw).into_owned();
let mut lines = text.lines();
// 1. 解析请求行
let request_line = lines
.next()
.ok_or_else(|| ServerError::HttpParse("empty request".into()))?;
let parts: Vec<&str> = request_line.split_whitespace().collect();
if parts.len() != 3 {
return Err(ServerError::HttpParse("invalid request line".into()));
}
let method = parts[0].to_uppercase();
let path = parts[1].to_string();
let version = parts[2].to_string();
// 2. 解析请求头
let mut headers = HashMap::new();
for line in lines {
if line.is_empty() {
break; // 空行 = 头部结束
}
if let Some((key, value)) = line.split_once(": ") {
headers.insert(key.to_string(), value.to_string());
}
}
// 3. 解析 body (根据 Content-Length)
let content_length = headers
.get("Content-Length")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let body = if content_length > 0 {
if let Some(pos) = find_body_start(raw) {
raw[pos..pos + content_length].to_vec()
} else {
Vec::new()
}
} else {
Vec::new()
};
Ok(HttpRequest { method, path, version, headers, body })
}
/// 获取查询参数 (对应 C++ 的 parseQuery)
///
/// 示例: /api/echo?message=hello → {"message": "hello"}
pub fn query_params(&self) -> HashMap<String, String> {
let mut params = HashMap::new();
if let Some(query_str) = self.path.split_once('?').map(|(_, q)| q) {
for pair in query_str.split('&') {
if let Some((key, value)) = pair.split_once('=') {
params.insert(
urldecode(key),
urldecode(value),
);
}
}
}
params
}
}
/// 查找请求体起始位置 (\r\n\r\n 之后)
fn find_body_start(raw: &[u8]) -> Option<usize> {
for i in 0..raw.len().saturating_sub(3) {
if &raw[i..i + 4] == b"\r\n\r\n" {
return Some(i + 4);
}
}
None
}
/// URL 解码 (简化版)
fn urldecode(s: &str) -> String {
s.replace("%20", " ").replace("+", " ")
}
/// HTTP 响应结构体 — 对标 C++ 的 HttpResponse
///
/// 支持: 多状态码、自定义 headers、Content-Length 自动计算
#[derive(Debug)]
pub struct HttpResponse {
pub status_code: u16,
pub status_text: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl HttpResponse {
pub fn new(status_code: u16, status_text: &str) -> Self {
Self {
status_code,
status_text: status_text.into(),
headers: Vec::new(),
body: Vec::new(),
}
}
/// 200 OK
pub fn ok(content_type: &str, body: Vec<u8>) -> Self {
let mut res = Self::new(200, "OK");
res.set_header("Content-Type", content_type);
res.body = body;
res
}
/// 404 Not Found
pub fn not_found() -> Self {
let body = b"<h1>404 Not Found</h1>".to_vec();
let mut res = Self::new(404, "Not Found");
res.set_header("Content-Type", "text/html; charset=utf-8");
res.body = body;
res
}
/// 设置响应头 (自动去重)
pub fn set_header(&mut self, key: &str, value: &str) {
for entry in &mut self.headers {
if entry.0.eq_ignore_ascii_case(key) {
*entry = (key.to_string(), value.to_string());
return;
}
}
self.headers.push((key.to_string(), value.to_string()));
}
/// 序列化为 HTTP 响应字节
pub fn to_bytes(&self) -> Vec<u8> {
let status_line = format!("HTTP/1.1 {} {}\r\n", self.status_code, self.status_text);
let mut result = Vec::new();
result.extend_from_slice(status_line.as_bytes());
for (key, value) in &self.headers {
result.extend_from_slice(format!("{}: {}\r\n", key, value).as_bytes());
}
// Content-Length 自动计算
result.extend_from_slice(format!("Content-Length: {}\r\n", self.body.len()).as_bytes());
result.extend_from_slice(b"\r\n");
result.extend_from_slice(&self.body);
result
}
}
use std::collections::HashMap;
use std::sync::Arc;
use crate::error::ServerError;
use crate::request::HttpRequest;
use crate::response::HttpResponse;
/// 路由参数 (对应 C++ 的 RouteParams)
pub type RouteParams = HashMap<String, String>;
/// 处理函数类型
pub type Handler = Arc<
dyn Fn(&HttpRequest, &RouteParams) -> Result<HttpResponse, ServerError> + Send + Sync
>;
/// 路由表 — 对标 C++ 的 Router 类
///
/// 支持三种路由模式:
/// - 静态路由: `/api/users` → 精确匹配
/// - 动态路由: `/user/:id` → 匹配 `/user/123`,提取 `id=123`
/// - 通配符路由: `/static/*filepath` → 匹配 `/static/a/b/c`
pub struct Router {
routes: Vec<(String, String, Handler)>, // (method, pattern, handler)
}
impl Router {
pub fn new() -> Self {
Self { routes: Vec::new() }
}
/// 注册 GET 路由
pub fn get<F>(&mut self, path: &str, handler: F)
where
F: Fn(&HttpRequest, &RouteParams) -> Result<HttpResponse, ServerError>
+ Send + Sync + 'static,
{
self.add_route("GET", path, handler);
}
/// 注册 POST 路由
pub fn post<F>(&mut self, path: &str, handler: F)
where
F: Fn(&HttpRequest, &RouteParams) -> Result<HttpResponse, ServerError>
+ Send + Sync + 'static,
{
self.add_route("POST", path, handler);
}
/// 注册任意方法路由
pub fn add_route<F>(&mut self, method: &str, path: &str, handler: F)
where
F: Fn(&HttpRequest, &RouteParams) -> Result<HttpResponse, ServerError>
+ Send + Sync + 'static,
{
self.routes.push((
method.to_uppercase(),
path.to_string(),
Arc::new(handler),
));
}
/// 查找并执行处理函数
pub fn route(&self, req: &HttpRequest) -> Result<HttpResponse, ServerError> {
for (method, pattern, handler) in &self.routes {
if method != &req.method {
continue;
}
if let Some(params) = match_pattern(pattern, &req.path) {
return handler(req, ¶ms);
}
}
Err(ServerError::NotFound(format!("{} {}", req.method, req.path)))
}
}
/// URL 模式匹配 — 提取路由参数
fn match_pattern(pattern: &str, path: &str) -> Option<RouteParams> {
let pattern_parts: Vec<&str> = pattern.split('/').collect();
let path_parts: Vec<&str> = path.split('/').collect();
let mut params = RouteParams::new();
let mut pi = 0;
let mut pa = 0;
while pi < pattern_parts.len() && pa < path_parts.len() {
let p = pattern_parts[pi];
if p.is_empty() { pi += 1; pa += 1; continue; }
if let Some(name) = p.strip_prefix(':') {
params.insert(name.to_string(), path_parts[pa].to_string());
pi += 1; pa += 1;
} else if let Some(name) = p.strip_prefix('*') {
params.insert(name.to_string(), path_parts[pa..].join("/"));
return Some(params);
} else if p == path_parts[pa] {
pi += 1; pa += 1;
} else {
return None;
}
}
if pi == pattern_parts.len() && pa == path_parts.len() {
Some(params)
} else {
None
}
}
/// 连接状态机 — 对标 C++ 的 TcpConnection 状态管理
///
/// Rust 的 enum + match 实现状态机有巨大优势:
/// - C++ 用 int 枚举 + if-else,容易遗漏状态转换
/// - Rust 编译器强制穷举所有分支,非法转换在编译期报错
use tokio::net::TcpStream;
use std::net::SocketAddr;
#[derive(Debug)]
pub enum ConnectionState {
/// 正在建立连接
Connecting { addr: SocketAddr },
/// 已连接,可以收发数据
Connected { addr: SocketAddr },
/// 正在断开
Disconnecting { reason: DisconnectReason },
/// 已断开
Disconnected,
}
#[derive(Debug)]
pub enum DisconnectReason {
ClientClosed,
ServerClosed,
Timeout,
Error(String),
}
#[derive(Debug)]
pub enum ConnectionEvent {
Established,
DataReceived(Vec<u8>),
PeerClosed,
CloseRequested,
TimedOut,
Error(String),
}
impl ConnectionState {
/// 状态转换 — 编译器确保处理所有合法转换
pub fn transition(self, event: ConnectionEvent) -> Result<Self, String> {
match (&self, &event) {
(Self::Connecting { .. }, ConnectionEvent::Established) => {
match self {
Self::Connecting { addr } => Ok(Self::Connected { addr }),
_ => unreachable!(),
}
}
(Self::Connected { .. }, ConnectionEvent::PeerClosed) => {
Ok(Self::Disconnecting { reason: DisconnectReason::ClientClosed })
}
(Self::Connected { .. }, ConnectionEvent::CloseRequested) => {
Ok(Self::Disconnecting { reason: DisconnectReason::ServerClosed })
}
(Self::Connected { .. }, ConnectionEvent::TimedOut) => {
Ok(Self::Disconnecting { reason: DisconnectReason::Timeout })
}
(Self::Disconnecting { .. }, _) => {
Ok(Self::Disconnected)
}
_ => Err(format!("Invalid transition: {:?} + {:?}", self, event)),
}
}
}
use std::path::PathBuf;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use tracing::{error, info};
use crate::request::HttpRequest;
use crate::response::HttpResponse;
use crate::router::Router;
/// HTTP 服务器 — 对标 C++ 的 HttpServer + ThreadPool
///
/// tokio 自动管理线程池,无需手写 EventLoopThreadPool。
/// 每个连接通过 tokio::spawn 在独立 task 中处理。
const MAX_REQUEST_SIZE: usize = 64 * 1024; // 64KB 请求大小限制
pub struct HttpServer {
addr: String,
www_root: PathBuf,
router: Router,
}
impl HttpServer {
pub fn new(addr: &str, www_root: &str) -> Self {
Self {
addr: addr.to_string(),
www_root: PathBuf::from(www_root),
router: Router::new(),
}
}
/// 获取路由表的可变引用 (用于注册路由)
pub fn router_mut(&mut self) -> &mut Router {
&mut self.router
}
/// 启动服务器
///
/// 注意: 消费 self 获取所有权,避免 Arc::new(&self) 的生命周期问题
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind(&self.addr).await?;
info!("Server listening on http://{}", self.addr);
// 消费 self,获取 router 所有权
let router = Arc::new(self.router);
loop {
let (mut stream, addr) = match listener.accept().await {
Ok(conn) => conn,
Err(e) => {
error!("accept error: {}", e);
continue;
}
};
info!("New connection from: {}", addr);
let router = Arc::clone(&router);
tokio::spawn(async move {
// Keep-Alive 循环: 一个连接处理多个请求
loop {
// 读取请求 (限制大小)
let mut buf = vec![0u8; MAX_REQUEST_SIZE];
let n = match stream.read(&mut buf).await {
Ok(0) => return, // 连接关闭
Ok(n) => n,
Err(e) => {
error!("read error: {}", e);
return;
}
};
buf.truncate(n);
// 解析请求
let request = match HttpRequest::parse(&buf) {
Ok(req) => req,
Err(e) => {
error!("parse error: {}", e);
let _ = stream.write_all(
&HttpResponse::not_found().to_bytes()
).await;
return; // 解析失败断开连接
}
};
// 检查 Connection: close 头
let should_close = request
.headers
.get("Connection")
.map(|v| v == "close")
.unwrap_or(false);
// 路由处理
let response = match router.route(&request) {
Ok(res) => res,
Err(e) => {
error!("route error: {}", e);
HttpResponse::not_found()
}
};
// 发送响应
if let Err(e) = stream.write_all(&response.to_bytes()).await {
error!("write error: {}", e);
return;
}
// 如果客户端要求关闭连接,退出循环
if should_close {
return;
}
// 否则继续处理下一个请求 (Keep-Alive)
}
});
}
}
}
use http_server_rust::{HttpResponse, HttpServer, HttpRequest, RouteParams};
#[tokio::main]
async fn main() {
// 初始化日志
tracing_subscriber::fmt()
.with_env_filter("info")
.init();
// 创建服务器
let mut server = HttpServer::new("127.0.0.1:8080", "www");
// 注册路由 — 对标 C++ 的 server.GET(...)
server.router_mut().get("/", |_req: &HttpRequest, _params: &RouteParams| {
let html = include_str!("../www/index.html");
Ok(HttpResponse::ok("text/html; charset=utf-8", html.as_bytes().to_vec()))
});
// 动态路由 — 对标 C++ 的 /user/:id
server.router_mut().get("/user/:id", |_req, params| {
let id = params.get("id").unwrap();
let body = format!(r#"{{"user_id": "{}"}}"#, id);
Ok(HttpResponse::ok("application/json", body.into_bytes()))
});
// 查询参数 — 对标 C++ 的 parseQuery()
server.router_mut().get("/api/echo", |req, _params| {
let query = req.query_params();
let message = query.get("message").map(|s| s.as_str()).unwrap_or("none");
let body = format!(r#"{{"echo": "{}"}}"#, message);
Ok(HttpResponse::ok("application/json", body.into_bytes()))
});
server.router_mut().post("/api/submit", |req, _params| {
let body_str = String::from_utf8_lossy(&req.body);
let response = format!(r#"{{"received": "{}"}}"#, body_str);
Ok(HttpResponse::ok("application/json", response.into_bytes()))
});
server.router_mut().get("/api/health", |_req, _params| {
Ok(HttpResponse::ok("application/json", br#"{"status":"ok"}"#.to_vec()))
});
// 启动
if let Err(e) = server.run().await {
eprintln!("Server error: {}", e);
}
}
use axum::{response::Html, routing::get, Router, Json, extract::Query};
use serde::Deserialize;
use std::collections::HashMap;
use tower_http::services::ServeDir;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let app = Router::new()
.route("/", get(root_handler))
.route("/api/health", get(health_handler))
.route("/api/echo", get(echo_handler))
.nest_service("/static", ServeDir::new("www"));
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
.await
.unwrap();
tracing::info!("Server listening on http://127.0.0.1:8080");
axum::serve(listener, app).await.unwrap();
}
async fn root_handler() -> Html<&'static str> {
Html(include_str!("../www/index.html"))
}
async fn health_handler() -> &'static str {
r#"{"status":"ok"}"#
}
#[derive(Deserialize)]
struct EchoParams { message: Option<String> }
async fn echo_handler(Query(params): Query<EchoParams>) -> Json<HashMap<String, String>> {
let mut map = HashMap::new();
map.insert("echo".to_string(), params.message.unwrap_or("none".to_string()));
Json(map)
}
代码量对比:
| 模块 | 手写版 (路线 A) | axum 版 (路线 B) |
|---|---|---|
| error.rs | ~40 行 | ~40 行 |
| request.rs | ~100 行 | 不需要 |
| response.rs | ~80 行 | 不需要 |
| router.rs | ~100 行 | 不需要 |
| server.rs | ~100 行 | 不需要 |
| connection.rs | ~60 行 | 不需要 |
| main.rs | ~50 行 | ~40 行 |
| 总计 | ~530 行 | ~80 行 |
// src/request.rs 末尾
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_get_request() {
let raw = b"GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n";
let req = HttpRequest::parse(raw).unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.path, "/index.html");
assert_eq!(req.headers.get("Host").unwrap(), "localhost");
}
#[test]
fn test_parse_post_with_body() {
let raw = b"POST /api/data HTTP/1.1\r\nContent-Length: 11\r\n\r\nHello World";
let req = HttpRequest::parse(raw).unwrap();
assert_eq!(req.method, "POST");
assert_eq!(req.body, b"Hello World");
}
#[test]
fn test_parse_query_params() {
let raw = b"GET /api/echo?message=hello&name=world HTTP/1.1\r\n\r\n";
let req = HttpRequest::parse(raw).unwrap();
let params = req.query_params();
assert_eq!(params.get("message").unwrap(), "hello");
assert_eq!(params.get("name").unwrap(), "world");
}
#[test]
fn test_parse_empty_request() {
let raw = b"";
assert!(HttpRequest::parse(raw).is_err());
}
}
// tests/router_test.rs
use http_server_rust::{HttpRequest, HttpResponse, Router, RouteParams};
use std::collections::HashMap;
#[test]
fn test_static_route() {
let mut router = Router::new();
router.get("/hello", |_req, _params| {
Ok(HttpResponse::ok("text/plain", b"Hello".to_vec()))
});
let req = HttpRequest::parse(b"GET /hello HTTP/1.1\r\n\r\n").unwrap();
let res = router.route(&req).unwrap();
assert_eq!(res.status_code, 200);
}
#[test]
fn test_dynamic_route() {
let mut router = Router::new();
router.get("/user/:id", |_req, params| {
let id = params.get("id").unwrap();
Ok(HttpResponse::ok("text/plain", format!("User: {}", id).into_bytes()))
});
let req = HttpRequest::parse(b"GET /user/123 HTTP/1.1\r\n\r\n").unwrap();
let res = router.route(&req).unwrap();
assert!(String::from_utf8_lossy(&res.body).contains("User: 123"));
}
#[test]
fn test_404_not_found() {
let router = Router::new();
let req = HttpRequest::parse(b"GET /nonexistent HTTP/1.1\r\n\r\n").unwrap();
assert!(router.route(&req).is_err());
}
# 终端 1: 启动服务器
cargo run --release
# 终端 2: curl 测试
curl -v http://127.0.0.1:8080/
curl -v http://127.0.0.1:8080/user/123
curl -v "http://127.0.0.1:8080/api/echo?message=hello"
curl -v -X POST -d "Hello" http://127.0.0.1:8080/api/submit
# 压力测试 (与 C++ 版本对齐)
sudo apt install -y apache2-utils
ab -n 10000 -c 100 -k http://127.0.0.1:8080/
# 或使用 wrk
wrk -t4 -c100 -d30s http://127.0.0.1:8080/
cargo check # 快速检查编译 (推荐开发时使用)
cargo build # 开发构建
cargo build --release # 发布构建 (高度优化)
cargo run # 编译并运行
cargo test # 运行所有测试
[profile.release]
opt-level = 3 # 最大优化级别
lto = true # 链接时优化
codegen-units = 1 # 更好的优化效果
strip = true # 去除符号表,减小二进制
panic = "abort" # panic 时直接 abort
FROM rust:1.85-alpine AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src/ ./src/
COPY www/ ./www/
RUN cargo build --release
FROM alpine:3.21
RUN apk add --no-cache ca-certificates
WORKDIR /opt/http_server
COPY --from=builder /app/target/release/http_server_rust .
COPY --from=builder /app/www/ ./www/
EXPOSE 8080
CMD ["./http_server_rust"]
# /etc/systemd/system/http-server.service
[Unit]
Description=Rust HTTP Server
After=network.target
[Service]
Type=simple
User=nobody
WorkingDirectory=/opt/http_server
ExecStart=/opt/http_server/http_server_rust
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
| C++ 习惯 | Rust 做法 | 原因 |
|---|---|---|
int fd = -1 表示空 | Option<TcpStream> | 类型安全,编译期检查 |
忘记 close(fd) | Drop 自动释放 | RAII 保证 |
std::mutex lock() | Mutex<T> 包裹数据 | 数据与锁绑定,编译期检查 |
多线程 shared_ptr | Arc<T> | 原子引用计数,Send + Sync 检查 |
throw/catch | Result<T, E> + ? | 编译期强制处理所有错误路径 |
nullptr 检查 | Option<T> 模式匹配 | 杜绝空指针解引用 |
| int 状态机 | enum + match | 编译器强制穷举所有状态转换 |
头文件 .h + .cpp | 单个 .rs 文件 | 模块系统 |
// ✅ 正确: 异步文件读取
let content = tokio::fs::read_to_string("www/index.html").await?;
// ❌ 错误: 同步读取会阻塞 tokio 工作线程
let content = std::fs::read_to_string("www/index.html")?;
// ⚠️ 如必须用同步操作:
let content = tokio::task::spawn_blocking(|| {
std::fs::read_to_string("www/index.html")
}).await??;
// ❌ 编译失败: 借用生命周期不明确
fn get_header(request: &HttpRequest) -> &str {
request.headers.get("Host")?.as_str()
}
// ✅ 显式标注生命周期
fn get_header<'a>(request: &'a HttpRequest, key: &str) -> Option<&'a str> {
request.headers.get(key).map(|s| s.as_str())
}
# 使用 sccache 缓存编译
cargo install sccache
export RUSTC_WRAPPER=sccache
# 使用 mold 链接器 (Linux)
# .cargo/config.toml:
# [target.x86_64-unknown-linux-gnu]
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
# 开发时用 cargo check (比 cargo build 快很多)
cargo check
以下问题来自对重构方案的审查,需要逐步修复。
request.rs 中 body 解析逻辑缺失,POST 请求 body 为空router.rs 需要支持 /user/:id 模式匹配router.rs 需要支持 /static/*filepath 模式?key=value 解析&str 引用而非 String 拷贝server.rs 中 Arc::new(&self.router) → Arc::new(self.router)| 资源 | 链接 |
|---|---|
| Rust 官方教程 | https://doc.rust-lang.org/book/ |
| Rust 异步编程 | https://rust-lang.github.io/async-book/ |
| mio (事件驱动 I/O) | https://docs.rs/mio/latest/mio/ |
| axum 文档 | https://docs.rs/axum/latest/axum/ |
| tokio 教程 | https://tokio.rs/tokio/tutorial |
| httparse (HTTP 解析) | https://docs.rs/httparse/latest/httparse/ |
| bytes (零拷贝缓冲) | https://docs.rs/bytes/latest/bytes/ |
编写日期: 2026-05-06
审查报告: 见
REFACTORING_REVIEW.md关键建议: 明确路线选择 → 补充缺失功能 → 深度利用 Rust 特性 (enum 状态机、编译期安全检查)