logo
0
0
WeChat Login

C++ HTTP Server → Rust 重构指南

目录

  1. 路线选择
  2. 原 C++ 项目架构回顾
  3. Rust 技术栈选型与对照
  4. 代码迁移策略
  5. 模块划分与实现
  6. 测试方法
  7. 构建与部署流程
  8. 附录:常见问题与陷阱
  9. 审查发现的问题与待办

1. 路线选择

开始前请先选择路线! 两条路线目标不同,不建议混用。

维度路线 A:手写 HTTP路线 B:axum 框架
适合人群想深入理解网络编程底层原理想快速构建生产级服务
核心技术mio + 手写事件循环 + 状态机解析axum + tokio + tower
对应 C++ 组件EventLoop, EPollPoller, Channel, Buffer全部由框架内部处理
代码量~1500-2500 行~200-400 行
开发时间17-24 小时4-6 小时
学习价值⭐⭐⭐⭐⭐⭐⭐
生产就绪需大量测试

推荐路径:先完成路线 A 学习底层原理,再迁移到路线 B 用于生产项目。


2. 原 C++ 项目架构回顾

2.1 核心架构:Main/Sub Reactor 多线程模型

┌──────────────────────────────────────────────────────────────┐
│  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 (静态路由 / 动态路由 / 通配符路由)                     │
└──────────────────────────────────────────────────────────────┘

2.2 C++ 完整功能清单

功能说明
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 加速小对象分配

3. Rust 技术栈选型与对照

3.1 技术对照表

概念C++Rust说明
构建系统CMakeCargo (内置)
包管理手动/vcpkgCargo (内置)
RAII构造/析构函数Drop traitRust 保证无泄漏
移动语义std::move所有权系统 (默认移动)编译期检查
错误处理异常/errnoResult<T, E> + ?编译期强制处理
空值nullptrOption<T>模式匹配,杜绝空指针
线程安全std::mutex + 约定Send + Sync 编译期检查Rust 核心优势
状态机int 枚举 + if-elseenum + match 穷举Rust 巨大优势
智能指针unique_ptr/shared_ptrBox<T>/Arc<T>
I/O 多路复用epoll (手写)miotokio (内置)

3.2 路线 A 依赖 (手写 HTTP)

[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"] }

3.3 路线 B 依赖 (axum 框架)

[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"

4. 代码迁移策略

4.1 推荐策略:自底向上,逐步替换

阶段 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 构建优化

4.2 迁移原则

原则说明
不要一次全部重写先让新项目能跑起来,再逐步替换模块
保留原测试用例用相同输入验证输出一致性
先功能后性能先保证逻辑正确,再优化
边迁移边写测试每个模块迁移完立即写测试
Git 频繁提交每个阶段完成后 commit

5. 模块划分与实现

5.1 项目文件布局

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

5.2 src/infra/error.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()
    }
}

5.3 src/infra/socket.rs — Socket RAII 封装

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)
}

5.4 src/http/request.rs — HTTP 请求解析

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("+", " ")
}

5.5 src/http/response.rs — HTTP 响应构建

/// 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
    }
}

5.6 src/http/router.rs — 路由分发

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, &params);
            }
        }
        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
    }
}

5.7 src/http/connection.rs — 连接状态机 (★ Rust 优势示例)

/// 连接状态机 — 对标 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)),
        }
    }
}

5.8 src/server/http_server.rs — HTTP 服务器主循环

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)
                }
            });
        }
    }
}

5.9 src/main.rs — 入口文件

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);
    }
}

5.10 路线 B: 用 axum 框架的极简版本

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 行

6. 测试方法

6.1 单元测试 (内联)

// 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());
    }
}

6.2 集成测试

// 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());
}

6.3 压力测试

# 终端 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/

7. 构建与部署流程

7.1 构建命令

cargo check                 # 快速检查编译 (推荐开发时使用)
cargo build                 # 开发构建
cargo build --release       # 发布构建 (高度优化)
cargo run                   # 编译并运行
cargo test                  # 运行所有测试

7.2 Release 构建优化

[profile.release]
opt-level = 3        # 最大优化级别
lto = true           # 链接时优化
codegen-units = 1    # 更好的优化效果
strip = true         # 去除符号表,减小二进制
panic = "abort"      # panic 时直接 abort

7.3 Docker 部署

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"]

7.4 systemd 服务

# /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

8. 附录:常见问题与陷阱

8.1 C++ 到 Rust 的思维转换

C++ 习惯Rust 做法原因
int fd = -1 表示空Option<TcpStream>类型安全,编译期检查
忘记 close(fd)Drop 自动释放RAII 保证
std::mutex lock()Mutex<T> 包裹数据数据与锁绑定,编译期检查
多线程 shared_ptrArc<T>原子引用计数,Send + Sync 检查
throw/catchResult<T, E> + ?编译期强制处理所有错误路径
nullptr 检查Option<T> 模式匹配杜绝空指针解引用
int 状态机enum + match编译器强制穷举所有状态转换
头文件 .h + .cpp单个 .rs 文件模块系统

8.2 async 与 blocking

// ✅ 正确: 异步文件读取
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??;

8.3 生命周期问题

// ❌ 编译失败: 借用生命周期不明确
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())
}

8.4 编译加速

# 使用 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

9. 审查发现的问题与待办

以下问题来自对重构方案的审查,需要逐步修复。

9.1 🔴 严重缺失 (必须补充)

  • 请求体解析: request.rs 中 body 解析逻辑缺失,POST 请求 body 为空
  • 动态路由: router.rs 需要支持 /user/:id 模式匹配
  • 通配符路由: router.rs 需要支持 /static/*filepath 模式
  • HTTP 方法路由: Router 需要区分 GET/POST/PUT/DELETE
  • Keep-Alive: server.rs 需要循环处理同一连接的多个请求
  • 请求大小限制: 防止超大请求耗尽内存

9.2 ⚠️ 重要缺失 (建议补充)

  • 查询参数解析: ?key=value 解析
  • 连接状态机: 利用 Rust enum 实现类型安全的状态管理
  • 超时管理: 连接超时和请求超时
  • Cookie 解析
  • 多状态码支持: 当前仅有 200/404
  • Chunked 传输编码: 大文件分块传输
  • fd 耗尽处理: Acceptor idleFd 优雅降级
  • 高水位保护: 发送缓冲区大小限制

9.3 💡 Rust 特性应用 (进阶)

  • 零拷贝设计: 使用 &str 引用而非 String 拷贝
  • Trait 抽象: 可替换的 Poller/Handler 接口
  • unsafe 安全注释: 所有 unsafe 块添加 SAFETY 注释
  • Arc 所有权: server.rsArc::new(&self.router)Arc::new(self.router)

9.4 📚 参考资源

资源链接
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 状态机、编译期安全检查)

About

使用rust的http协议实现

Language
Rust100%