上一篇已把库、二进制和依赖管理拆清本篇把任务核心挂到 HTTP用 Axum 声明路由、Serde 处理 JSON、Tokio 驱动连接并把状态、错误码和并发边界设计成可测试接口。一、痛点返回 200 不代表服务设计正确网络服务至少有四层传输层解析 HTTP适配层校验请求并映射响应应用层执行用例存储层维护数据。小示例可以放一文件但仍应保持函数边界避免 handler 同时解析 JSON、执行业务、访问数据库和拼错误文本。否则测试只能启动端口错误语义也散落各处。本篇选择 Axum因为它与 Tokio、Tower 生态衔接提取器和响应类型直接表达 handler 契约。先创建项目在Cargo.toml加axum 0.8、tokio { version 1, features [full] }、serde { version 1, features [derive] }。实际使用前应以官方当前文档核对兼容版本并提交锁文件。HTTP 语义要明确创建成功返回 201 和资源输入非法返回 400资源不存在返回 404内部故障返回 500但不把内部错误和敏感信息直接回给客户端。列表端点即使为空也返回 200 与空数组而非 404。二、原理提取器、状态和响应转换Axum handler 的参数是提取器如State、Path、Json返回类型实现IntoResponse。框架在调用前完成请求解析签名因此就是路由契约。共享内存状态使用ArcRwLockVecTaskArc 共享所有权读写锁允许多读单写。示例规模很小生产持久化应换数据库连接池。useaxum::{extract::State,http::StatusCode,routing::get,Json,Router};useserde::{Deserialize,Serialize};usestd::sync::{Arc,RwLock};#[derive(Clone, Serialize)]structTask{id:u64,title:String}#[derive(Deserialize)]structCreateTask{title:String}typeAppStateArcRwLockVecTask;asyncfnlist(State(state):StateAppState)-JsonVecTask{Json(state.read().expect(lock poisoned).clone())}asyncfncreate(State(state):StateAppState,Json(input):JsonCreateTask)-Result(StatusCode,JsonTask),(StatusCode,String){lettitleinput.title.trim();iftitle.is_empty(){returnErr((StatusCode::BAD_REQUEST,title must not be empty.into()));}letmuttasksstate.write().map_err(|_|(StatusCode::INTERNAL_SERVER_ERROR,state unavailable.into()))?;lettaskTask{id:tasks.len()asu641,title:title.into()};tasks.push(task.clone());Ok((StatusCode::CREATED,Json(task)))}#[tokio::main]asyncfnmain(){letstate:AppStateArc::new(RwLock::new(Vec::new()));letappRouter::new().route(/tasks,get(list).post(create)).with_state(state);letlistenertokio::net::TcpListener::bind(127.0.0.1:3000).await.unwrap();println!(listeninghttp://127.0.0.1:3000);axum::serve(listener,app).await.unwrap();}运行输出listeninghttp://127.0.0.1:3000启动后执行curl -i -H content-type: application/json -d {title:学习 Axum} http://127.0.0.1:3000/tasks应得到 201 和任务 JSON再 GET 同一路径得到数组。服务仅绑定回环地址适合本机实验容器部署是否绑定0.0.0.0要结合网络边界和访问控制决定。三、实现先测试 Router再测试真实端口路由本质是 Tower service可直接发送内存请求不必抢占端口。为了提供第二个完全独立可执行示例下面用标准库实现一个最小 HTTP 健康检查它展示协议边界不替代成熟框架。usestd::io::{Read,Write};usestd::net::{TcpListener,TcpStream};usestd::thread;fnhandle(mutstream:TcpStream){letmutbuffer[0_u8;1024];letsizestream.read(mutbuffer).unwrap_or(0);letrequestString::from_utf8_lossy(buffer[..size]);let(status,body)ifrequest.starts_with(GET /health ){(200 OK,r#{status:ok}#)}else{(404 Not Found,r#{error:not found}#)};letresponseformat!(HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body},body.len());stream.write_all(response.as_bytes()).unwrap();}fnmain(){letlistenerTcpListener::bind(127.0.0.1:3001).unwrap();println!(healthhttp://127.0.0.1:3001/health);forstreaminlistener.incoming().take(1){matchstream{Ok(stream){thread::spawn(||handle(stream)).join().unwrap();}Err(error)eprintln!(accept_error{error}),}}}运行输出healthhttp://127.0.0.1:3001/health这个程序只接受一次连接后退出方便复现它没有完整处理分块编码、请求体、慢客户端和协议攻击生产环境必须用维护良好的 HTTP 实现。对 Axum Router 测试时应断言状态、Content-Type 和 JSON 结构同时覆盖空标题、错误方法和未知路径。四、踩坑在异步 handler 中阻塞与持锁标准库RwLock只适合极短且不跨.await的临界区。上例在锁内仅分配编号并 push没有等待。若要访问数据库应先复制需要的信息、释放锁再异步调用。更好的生产设计是连接池和事务用数据库唯一约束生成编号避免多实例各自按长度分配导致冲突。不要无限制读取请求体框架默认限制也应按业务核对超大 JSON 会消耗内存。设置请求超时、并发限制和追踪中间件但要理解顺序超时包在什么层会决定是否涵盖排队时间。反向代理后的客户端 IP、TLS 和大小限制需要端到端配置不能只看应用。错误响应应有稳定机器码、面向用户的消息和请求关联标识。日志记录内部原因及 trace id不记录密码和令牌。健康检查分存活与就绪存活只说明进程能响应就绪才检查关键依赖把数据库短暂故障当存活失败可能引发重启风暴。五、验证从功能到关闭路径依次验证创建、列表、非法 JSON、空标题、未知路由和并发创建。启动真实端口做一次 curl 冒烟测试再对 Router 做快速确定的单元集成测试。发送 SIGTERM 时停止接受新连接给在途请求截止时间刷新日志后退出Axum 支持 graceful shutdown应在部署前实际演练。任务服务已经能通过网络访问但还缺配置、持久化、可观测性和交付说明。下一篇收官会把此前所有方法组装成一个可落地小项目给出配置加载、原子文件保存、测试与发布验收清单。参考来源Axum官方文档Tokio构建应用教程Serde官方文档RFC 9110HTTP Semantics 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《Rust 入门实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。