基于 Rust 与 axum 实现的 HTTP Mock 服务。程序从 JSON 规格文件中加载请求匹配规则,在运行时根据方法、路径、Header、Body 进行顺序匹配,并返回预先定义的响应内容。
这个仓库当前更偏向“轻量可控的本地 Mock 工具”,实现简单,便于直接阅读、扩展和嵌入测试流程。
- 基于
axum 0.8提供 HTTP 服务 - 从 JSON 文件加载 Mock 规则
- 支持路径参数模式,例如
/api/users/:id - 支持正则路径模式,例如
/api/products/[0-9]+ - 支持按 Header 与 JSON Body 精确匹配
- 支持配置响应状态码、Header、Body、延迟
- 支持通过
SIGHUP热重载规格文件 - 支持
SIGTERM优雅停机 - 内置单元测试与集成测试
程序入口位于 src/main.rs。
启动流程如下:
- 初始化
tracing_subscriber日志系统 - 从
config.toml读取配置;如果文件不存在则回退到默认配置 - 根据配置中的
spec_path加载规格文件 - 将规格编译为带正则的内部结构并放入共享存储
- 启动
SIGHUP监听任务,支持热重载 - 创建 axum
Router,监听配置中的地址并开始服务 - 收到
SIGTERM时执行优雅关闭
配置逻辑位于 src/config.rs。
当前支持的配置项:
server_addr:监听地址,默认0.0.0.0:3000body_limit:请求体读取上限,默认65536spec_path:规格文件路径,默认spec.json
环境变量优先级:
MOCK_CONFIG:覆盖默认配置文件路径MOCK_SPEC:覆盖配置文件中的spec_path
这意味着即使仓库根目录没有 config.toml,程序也仍会用默认值启动;但如果默认的 spec.json 不存在,初始化规格存储时会报错退出。
请求处理位于 src/app.rs。
当前路由非常直接:
//{*path}
所有 HTTP 方法都会进入统一的 handle_request 流程:
- 提取方法与路径
- 读取所有请求头到
HashMap<String, String> - 按
body_limit读取请求体 - 尝试把请求体解析为 JSON
- 调用
spec::match_request在内存中的规格列表里顺序匹配 - 如果命中则返回配置响应;否则返回
404 {"error":"no matching spec"}
Spec 编译位于 src/spec/compiler.rs。
项目不会在每次请求时重新解释路径规则,而是在加载时先把路径编译成正则:
- 普通路径如
/api/users会被转成精确匹配正则 :param形式会转成带参数名的命名捕获组- 包含
[、(、\的路径会被当成正则表达式直接使用
编译后的运行时结构是 SpecWithPattern:
- 原始
Spec - 预编译
Regex
这样请求阶段只需要执行正则匹配,不必重复做路径转换。
匹配逻辑位于 src/spec/matcher.rs。
当前匹配顺序是:
- HTTP 方法完全相等
- 路径正则匹配
- Header 全量子集匹配
- Body 匹配
几个关键实现细节:
- 匹配是“按 Spec 声明顺序优先”,找到第一个命中的规则即停止
- Header 名按大小写无关方式匹配,例如
Content-Type与content-type会被视为同一个键 - 请求体会先被解析成 JSON,再做比较;因此当前实现更适合匹配 JSON 请求
- Body 中字符串值
"*"或空字符串""被视为通配 - 响应延迟在命中后通过
tokio::time::sleep实现
规格存储位于 src/spec/store.rs。
共享存储类型是:
pub type SharedSpecStore = Arc<RwLock<Vec<SpecWithPattern>>>;设计含义:
- 读取请求时走读锁,开销较低
- 热重载时重新编译整个 Spec 列表,再一次性替换
- 重新加载失败时保留旧配置,不会把当前服务状态清空
测试分成两类:
- 源码内单元测试:覆盖编译器、存储、基础匹配
- tests/spec_tests.rs:覆盖完整请求路径、404、延迟、热更新等行为
仓库还提供了 integration.sh 作为基于 curl 的集成验证脚本。
这部分在使用前值得先看清:
- 当前请求体匹配本质上是 JSON 匹配,不是通用文本匹配
- Header 名按 HTTP 语义做大小写无关匹配
- 路径参数可以通过
{{param}}形式注入响应体模板 - 没有请求转发、模板渲染、场景状态机等高级能力
SIGHUP/SIGTERM使用tokio::signal::unix,更适合类 Unix 环境
- Rust stable 工具链
- Cargo
- 建议在 macOS 或 Linux 上运行
cargo build或构建发布版:
cargo build --releasecargo fmt
cargo clippy --all-targets这一节按“从零启动一个可用的 Mock 服务”来写,直接对应当前仓库实现。
程序默认读取根目录下的 config.toml。如果没有这个文件,会自动使用默认配置:
server_addr = "0.0.0.0:3000"
body_limit = 65536
spec_path = "spec.json"你可以直接新建一个最小配置文件:
server_addr = "127.0.0.1:3000"
body_limit = 65536
spec_path = "spec.json"如果你不想创建新文件,也可以直接使用仓库中的测试配置:
MOCK_CONFIG=test-config.toml MOCK_SPEC=test-spec.json cargo runSpec 文件是一个 JSON 数组,每一项定义一条请求规则和对应响应。
最小可运行示例:
[
{
"name": "get-user",
"httpRequest": {
"method": "GET",
"path": "/api/users/:id"
},
"httpResponse": {
"statusCode": 200,
"headers": {
"content-type": "application/json"
},
"body": "{\"id\":\"{{id}}\",\"name\":\"zhangsan\"}"
}
},
{
"name": "login-success",
"httpRequest": {
"method": "POST",
"path": "/api/login",
"body": {
"username": "admin",
"password": "123456"
}
},
"httpResponse": {
"statusCode": 200,
"headers": {
"content-type": "application/json"
},
"body": "{\"token\":\"fake-jwt-token\"}"
}
},
{
"name": "login-failed",
"httpRequest": {
"method": "POST",
"path": "/api/login"
},
"httpResponse": {
"statusCode": 401,
"headers": {
"content-type": "application/json"
},
"body": "{\"error\":\"invalid credentials\"}"
}
}
]建议:
name用于日志与调试,保持唯一且可读- Header 名支持大小写混写,但建议团队内部保持一致风格
- 如果需要兜底规则,把它放在更具体规则后面
使用默认配置启动:
cargo run使用自定义配置启动:
MOCK_CONFIG=./config.toml cargo run只覆盖 Spec 路径:
MOCK_SPEC=./spec.json cargo run发布版启动:
./target/release/mockservercurl http://127.0.0.1:3000/api/users/100预期返回:
{"id":"100","name":"zhangsan"}这里的 100 来自请求路径 /api/users/100 中的 :id,通过响应体模板里的 {{id}} 注入。
curl -X POST http://127.0.0.1:3000/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"123456"}'预期返回:
{"token":"fake-jwt-token"}curl -i http://127.0.0.1:3000/not-found预期返回状态码 404,响应体为:
{"error":"no matching spec"}如果你希望按正则匹配路径,可以在 path 中直接写正则模式:
{
"name": "get-product",
"httpRequest": {
"method": "GET",
"path": "/api/products/[0-9]+"
},
"httpResponse": {
"statusCode": 200,
"body": "{\"id\":1,\"name\":\"product\"}"
}
}这样 /api/products/1、/api/products/999 都会命中。
延迟配置写在 httpResponse.delay:
{
"timeUnit": "SECONDS",
"value": 2
}支持的时间单位:
MILLISECONDSSECONDSMINUTES
示例:
{
"name": "slow-product-api",
"httpRequest": {
"method": "GET",
"path": "/api/products/[0-9]+"
},
"httpResponse": {
"statusCode": 200,
"body": "{\"ok\":true}",
"delay": {
"timeUnit": "SECONDS",
"value": 2
}
}
}修改 Spec 文件后,向进程发送 SIGHUP:
kill -HUP <pid>建议流程:
- 先修改
spec.json - 用 JSON 校验工具确认格式无误
- 执行
kill -HUP <pid> - 再次用
curl验证新规则
如果重载失败,程序会记录错误日志,但会继续使用旧的已加载规则。
默认日志级别使用 RUST_LOG 控制;未设置时为 info。
示例:
RUST_LOG=debug cargo run你可以看到:
- 启动日志
- 配置加载日志
- 请求匹配日志
- 热重载成功或失败日志
向进程发送 SIGTERM 可触发优雅停机:
kill -TERM <pid>在开发环境中,Ctrl+C 是否按预期退出取决于外层运行方式;当前代码显式处理的是 SIGTERM。
config.toml 示例:
server_addr = "0.0.0.0:3000"
body_limit = 65536
spec_path = "spec.json"字段说明:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
server_addr |
String |
0.0.0.0:3000 |
服务监听地址 |
body_limit |
usize |
65536 |
请求体读取上限 |
spec_path |
String |
spec.json |
Spec 文件路径 |
环境变量覆盖关系:
单条 Spec 结构:
{
"name": "rule-name",
"httpRequest": {
"method": "GET",
"path": "/api/users/:id",
"headers": {
"content-type": "application/json"
},
"body": {
"id": 1
}
},
"httpResponse": {
"statusCode": 200,
"headers": {
"content-type": "application/json"
},
"body": "{\"ok\":true}",
"delay": {
"timeUnit": "SECONDS",
"value": 1
}
}
}字段说明:
name:规则名称httpRequest.method:请求方法,当前是精确匹配httpRequest.path:支持普通路径、路径参数、正则httpRequest.headers:可选,定义后必须全部匹配httpRequest.body:可选,适用于 JSON 匹配httpResponse.statusCode:响应状态码httpResponse.headers:可选,响应头httpResponse.body:响应体字符串,支持使用{{param}}引用路径参数httpResponse.delay:可选,响应延迟
匹配优先级:
- 规则列表从前到后
- 更具体的规则应放在前面
- 更宽泛的兜底规则应放在后面
运行全部测试:
cargo test运行带日志测试:
RUST_LOG=debug cargo test -- --nocapture运行单个集成测试:
cargo test test_normal_matching -- --nocapture使用脚本做端到端验证:
- 先启动服务
- 再执行:
chmod +x integration.sh
./integration.sh仓库提供了 Dockerfile,但它依赖自定义基础镜像 rust-builder:1.0.0。如果你的环境没有这个镜像,需要先自行准备,或者按你的 CI/CD 规范改成公开可用的 Rust 基础镜像。
此外,容器运行时还需要把实际使用的 config.toml 与 Spec 文件一起提供进去,否则程序仍会因为找不到默认 spec.json 而无法启动。
.
├── src
│ ├── main.rs
│ ├── app.rs
│ ├── config.rs
│ ├── lib.rs
│ └── spec
│ ├── mod.rs
│ ├── compiler.rs
│ ├── matcher.rs
│ ├── models.rs
│ └── store.rs
├── tests
│ └── spec_tests.rs
├── test-config.toml
├── test-spec.json
├── integration.sh
└── README.md
- 支持文本请求体与
Content-Type分支匹配 - 支持自动文件监听热重载,而不是手动发送
SIGHUP - 支持场景状态、优先级、次数限制等更完整的 Mock 能力
