feat(config): 添加 JSON 配置文件支持 + gzip_enabled 控制 + README 修正
- config.c/config.h: 极简 JSON 解析器,支持数字/字符串/布尔//注释 - cocoon_config_t 新增 gzip_enabled 字段(默认 true) - main.c: -c <file> 加载配置,--no-gzip 禁用压缩,命令行覆盖配置 - 修复 -r 参数 strdup 崩溃(避免 free 野指针) - 修复 signal handler 双重 free 问题 - 单元测试: test_config.c 17 个测试全部通过 - 集成测试: 32 项全部通过 - README 修正过时描述(配置方式、安全设计、-v 说明)
This commit is contained in:
parent
c036615d58
commit
3801f3ac0d
@ -30,9 +30,13 @@
|
|||||||
- [x] C 语言单元测试框架 ✅ 2026-06-03(Unity 框架,99 个测试全部通过)
|
- [x] C 语言单元测试框架 ✅ 2026-06-03(Unity 框架,99 个测试全部通过)
|
||||||
|
|
||||||
### Phase 3 — 扩展
|
### Phase 3 — 扩展
|
||||||
- [ ] 配置文件支持(JSON / YAML)
|
- [x] **配置文件支持** — JSON 配置替代纯命令行 ✅ 2026-06-04
|
||||||
- [ ] 虚拟主机 / 多站点
|
- `config.c` / `config.h`:极简 JSON 解析器(数字、字符串、布尔、注释)
|
||||||
- [ ] 反向代理支持
|
- `cocoon_config_t` 结构体:root_dir / port / threaded / num_workers / max_connections / timeout_ms / log_level / gzip_enabled
|
||||||
|
- `config_merge()`:命令行参数覆盖配置文件
|
||||||
|
- `cocoon.json` 示例配置
|
||||||
|
- `--no-gzip` 命令行选项禁用压缩
|
||||||
|
- [ ] Brotli 压缩
|
||||||
- [ ] HTTPS / TLS
|
- [ ] HTTPS / TLS
|
||||||
- [ ] HTTP/2
|
- [ ] HTTP/2
|
||||||
|
|
||||||
@ -45,24 +49,38 @@
|
|||||||
|
|
||||||
- 编译通过,零警告(除 coco 子模块的 linker .note.GNU-stack 提示)
|
- 编译通过,零警告(除 coco 子模块的 linker .note.GNU-stack 提示)
|
||||||
- **32 项集成测试全部通过**(GET/HEAD/POST/404/Range/304/gzip/MIME/目录浏览/路径防护/**文件上传**)
|
- **32 项集成测试全部通过**(GET/HEAD/POST/404/Range/304/gzip/MIME/目录浏览/路径防护/**文件上传**)
|
||||||
- **99 个单元测试全部通过**(Unity 框架,覆盖 http.c / static.c / multipart.c 核心逻辑)
|
- **109 个单元测试全部通过**(Unity 框架,覆盖 http.c / static.c / multipart.c / config.c 核心逻辑)
|
||||||
- 压测数据:wrk -t4 -c100 -d10s → 16,179 RPS,平均延迟 59.86μs
|
- 压测数据:wrk -t4 -c100 -d10s → 16,179 RPS,平均延迟 59.86μs
|
||||||
- POST 支持:JSON 和 form-urlencoded 回显,multipart 文件上传,Content-Length 解析,8MB 上限
|
- POST 支持:JSON 和 form-urlencoded 回显,multipart 文件上传,Content-Length 解析,8MB 上限
|
||||||
|
- **配置文件支持**:JSON 格式,8 个字段,命令行参数可覆盖
|
||||||
- README / Makefile 已更新
|
- README / Makefile 已更新
|
||||||
|
|
||||||
## 待办池
|
## 待办池
|
||||||
|
|
||||||
1. **Brotli 压缩** — 在 Gzip 基础上添加 br 支持
|
1. **[高] Brotli 压缩** — 比 gzip 更高压缩率,现代浏览器均支持
|
||||||
2. **配置文件支持** — JSON/YAML 配置替代纯命令行
|
2. **[高] HTTPS / TLS** — 生产部署必备
|
||||||
3. **server.c 单元测试** — 需要 mock socket 和 coco 协程环境
|
3. **[中] server.c 单元测试** — 需要 mock socket 和 coco 协程环境
|
||||||
4. **性能优化** — io_uring 性能调优、连接池优化
|
4. **[中] 完善 Doxygen 中文注释** — 所有模块的公共 API 需要完整文档
|
||||||
5. **HTTPS / TLS** — 添加 SSL 支持
|
5. **[低] HTTP/2 多路复用** — 长期演进方向
|
||||||
6. **Signal handler cleanup bug** — 关闭服务器时 `munmap_chunk(): invalid pointer` 崩溃,需修复信号处理器的资源清理逻辑
|
|
||||||
7. **完善中文注释** — 所有模块的 Doxygen 风格注释补充完整
|
|
||||||
8. **单元测试二进制文件添加 gitignore** — 确保 tests/unit/test_* 不会被误提交
|
|
||||||
|
|
||||||
## 最近行动记录
|
## 最近行动记录
|
||||||
|
|
||||||
|
- 2026-06-04: **本轮行动 — 配置文件支持 + README 修正 + bug 修复**
|
||||||
|
- `config.c` / `config.h`:极简 JSON 配置解析器(支持数字、字符串、布尔 true/false、// 注释)
|
||||||
|
- `cocoon_config_t` 扩展 `gzip_enabled` 字段(默认 true)
|
||||||
|
- `main.c`:`-c <file>` 加载配置文件,`--no-gzip` 禁用压缩,命令行参数覆盖配置文件
|
||||||
|
- `config_merge()`:命令行显式指定值覆盖配置文件,新增 `has_gzip_enabled` 参数
|
||||||
|
- **修复 `-r` 参数 strdup 崩溃**:`parse_args` 中 `config->root_dir = strdup(argv[i])`,避免 `free()` 野指针
|
||||||
|
- **修复 signal handler 双重 free**:`main()` 中 server 指针仅在 `server != NULL` 时调用 `server_destroy()`
|
||||||
|
- 单元测试:`test_config.c` 17 个测试(配置加载、merge、边界条件、gzip_enabled)全部通过
|
||||||
|
- 集成测试:32 项全部通过,配置文件启动验证通过
|
||||||
|
- `cocoon.json` 示例配置更新,添加 `gzip_enabled` 字段
|
||||||
|
- README 修正:
|
||||||
|
- "极简配置" → "配置文件"(已支持 JSON 配置)
|
||||||
|
- 安全设计:"仅允许 GET/HEAD" → "支持 GET/HEAD/POST"
|
||||||
|
- `-v` 参数说明:"显示版本号" → "详细日志输出(debug 级别)"
|
||||||
|
- 路线图:配置文件支持打勾,添加 Brotli 压缩待办
|
||||||
|
- 推送到 main
|
||||||
- 2026-06-04: **本轮行动 — multipart 文件上传**
|
- 2026-06-04: **本轮行动 — multipart 文件上传**
|
||||||
- 实现 `multipart.c` / `multipart.h`:boundary 提取、multipart 解析、part 内存管理
|
- 实现 `multipart.c` / `multipart.h`:boundary 提取、multipart 解析、part 内存管理
|
||||||
- `server.c` 集成:POST 请求检测 multipart,保存文件到 `root_dir/uploads/`
|
- `server.c` 集成:POST 请求检测 multipart,保存文件到 `root_dir/uploads/`
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
| 🛡️ 资源限制 | 空闲连接超时(默认 30s)+ 最大并发连接数限制 |
|
| 🛡️ 资源限制 | 空闲连接超时(默认 30s)+ 最大并发连接数限制 |
|
||||||
| 📝 分级日志 | error / warn / info / debug 四级输出,命令行可调 |
|
| 📝 分级日志 | error / warn / info / debug 四级输出,命令行可调 |
|
||||||
| 🌐 POST 回显 | 支持 JSON / form-urlencoded 请求体回显(API 测试) |
|
| 🌐 POST 回显 | 支持 JSON / form-urlencoded 请求体回显(API 测试) |
|
||||||
| 🔧 极简配置 | 纯命令行启动,无需配置文件 |
|
| 🔧 配置文件 | JSON 配置文件 + 命令行参数覆盖,生产部署友好 |
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ Options:
|
|||||||
-m <num> 最大并发连接数限制(默认 10000)
|
-m <num> 最大并发连接数限制(默认 10000)
|
||||||
-o <ms> 连接空闲超时毫秒数(默认 30000)
|
-o <ms> 连接空闲超时毫秒数(默认 30000)
|
||||||
-l <level> 日志级别:debug, info, warn, error(默认 info)
|
-l <level> 日志级别:debug, info, warn, error(默认 info)
|
||||||
-v 显示版本号
|
-v 详细日志输出(等同于 -l debug)
|
||||||
-h 显示帮助
|
-h 显示帮助
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ static_send_error(fd, 404, true);
|
|||||||
- **路径遍历防护**:自动过滤 `../`,拒绝超出根目录的访问
|
- **路径遍历防护**:自动过滤 `../`,拒绝超出根目录的访问
|
||||||
- **目录隐藏**:不显示以 `.` 开头的隐藏文件
|
- **目录隐藏**:不显示以 `.` 开头的隐藏文件
|
||||||
- **HTML 转义**:目录列表中的文件名自动转义,防止 XSS
|
- **HTML 转义**:目录列表中的文件名自动转义,防止 XSS
|
||||||
- **请求方法过滤**:仅允许 `GET` / `HEAD`,拒绝其他方法
|
- **请求方法过滤**:支持 `GET` / `HEAD` / `POST`,拒绝其他方法
|
||||||
|
|
||||||
## 性能
|
## 性能
|
||||||
|
|
||||||
@ -222,9 +222,9 @@ make bench
|
|||||||
- [x] 连接空闲超时 + 最大并发限制
|
- [x] 连接空闲超时 + 最大并发限制
|
||||||
- [x] 分级日志系统
|
- [x] 分级日志系统
|
||||||
- [x] POST 请求体解析(JSON / form-urlencoded 回显)
|
- [x] POST 请求体解析(JSON / form-urlencoded 回显)
|
||||||
|
- [x] 配置文件支持(JSON)
|
||||||
- [ ] HTTPS / TLS 支持
|
- [ ] HTTPS / TLS 支持
|
||||||
- [ ] HTTP/2 多路复用
|
- [ ] HTTP/2 多路复用
|
||||||
- [ ] 配置文件支持(JSON / YAML)
|
|
||||||
|
|
||||||
## 构建与安装
|
## 构建与安装
|
||||||
|
|
||||||
|
|||||||
1
cocoon.h
1
cocoon.h
@ -36,6 +36,7 @@ typedef struct cocoon_config {
|
|||||||
uint32_t max_connections; /**< 最大并发连接数(0 = 无限制) */
|
uint32_t max_connections; /**< 最大并发连接数(0 = 无限制) */
|
||||||
uint32_t timeout_ms; /**< 连接空闲超时毫秒(0 = 默认30000) */
|
uint32_t timeout_ms; /**< 连接空闲超时毫秒(0 = 默认30000) */
|
||||||
log_level_t log_level; /**< 日志级别 */
|
log_level_t log_level; /**< 日志级别 */
|
||||||
|
bool gzip_enabled; /**< 是否启用 gzip 压缩(默认 true) */
|
||||||
} cocoon_config_t;
|
} cocoon_config_t;
|
||||||
|
|
||||||
/* === 服务器生命周期 API === */
|
/* === 服务器生命周期 API === */
|
||||||
|
|||||||
@ -5,5 +5,6 @@
|
|||||||
"num_workers": 4,
|
"num_workers": 4,
|
||||||
"max_connections": 10000,
|
"max_connections": 10000,
|
||||||
"timeout_ms": 30000,
|
"timeout_ms": 30000,
|
||||||
"log_level": "info"
|
"log_level": "info",
|
||||||
|
"gzip_enabled": true
|
||||||
}
|
}
|
||||||
|
|||||||
7
config.c
7
config.c
@ -268,6 +268,9 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
|
|||||||
config->log_level = str_to_log_level(v);
|
config->log_level = str_to_log_level(v);
|
||||||
free(v);
|
free(v);
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(key_str, "gzip_enabled") == 0) {
|
||||||
|
if (val.type == TOKEN_TRUE) config->gzip_enabled = true;
|
||||||
|
else if (val.type == TOKEN_FALSE) config->gzip_enabled = false;
|
||||||
}
|
}
|
||||||
/* 其他字段:忽略(未来扩展预留) */
|
/* 其他字段:忽略(未来扩展预留) */
|
||||||
|
|
||||||
@ -289,7 +292,8 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
|
|||||||
|
|
||||||
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
||||||
bool has_root_dir, bool has_port, bool has_workers,
|
bool has_root_dir, bool has_port, bool has_workers,
|
||||||
bool has_max_conn, bool has_timeout, bool has_log_level) {
|
bool has_max_conn, bool has_timeout, bool has_log_level,
|
||||||
|
bool has_gzip_enabled) {
|
||||||
if (!base || !cmdline) return;
|
if (!base || !cmdline) return;
|
||||||
|
|
||||||
/* 命令行显式指定的值覆盖配置文件 */
|
/* 命令行显式指定的值覆盖配置文件 */
|
||||||
@ -302,6 +306,7 @@ void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
|||||||
if (has_max_conn) base->max_connections = cmdline->max_connections;
|
if (has_max_conn) base->max_connections = cmdline->max_connections;
|
||||||
if (has_timeout) base->timeout_ms = cmdline->timeout_ms;
|
if (has_timeout) base->timeout_ms = cmdline->timeout_ms;
|
||||||
if (has_log_level) base->log_level = cmdline->log_level;
|
if (has_log_level) base->log_level = cmdline->log_level;
|
||||||
|
if (has_gzip_enabled) base->gzip_enabled = cmdline->gzip_enabled;
|
||||||
/* threaded 是 flag 参数,命令行指定了就用命令行的 */
|
/* threaded 是 flag 参数,命令行指定了就用命令行的 */
|
||||||
if (cmdline->threaded) base->threaded = true;
|
if (cmdline->threaded) base->threaded = true;
|
||||||
}
|
}
|
||||||
|
|||||||
3
config.h
3
config.h
@ -42,6 +42,7 @@ bool config_load_from_file(const char *path, cocoon_config_t *config);
|
|||||||
*/
|
*/
|
||||||
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
||||||
bool has_root_dir, bool has_port, bool has_workers,
|
bool has_root_dir, bool has_port, bool has_workers,
|
||||||
bool has_max_conn, bool has_timeout, bool has_log_level);
|
bool has_max_conn, bool has_timeout, bool has_log_level,
|
||||||
|
bool has_gzip_enabled);
|
||||||
|
|
||||||
#endif /* COCOON_CONFIG_H */
|
#endif /* COCOON_CONFIG_H */
|
||||||
|
|||||||
10
main.c
10
main.c
@ -51,6 +51,7 @@ static void print_usage(const char *prog) {
|
|||||||
printf(" -o <ms> 连接空闲超时毫秒(默认 30000)\n");
|
printf(" -o <ms> 连接空闲超时毫秒(默认 30000)\n");
|
||||||
printf(" -l <level> 日志级别: error, warn, info, debug(默认 info)\n");
|
printf(" -l <level> 日志级别: error, warn, info, debug(默认 info)\n");
|
||||||
printf(" -v 详细日志输出(等同于 -l debug)\n");
|
printf(" -v 详细日志输出(等同于 -l debug)\n");
|
||||||
|
printf(" --no-gzip 禁用 gzip 压缩\n");
|
||||||
printf(" -h 显示此帮助\n");
|
printf(" -h 显示此帮助\n");
|
||||||
printf("\nExample:\n");
|
printf("\nExample:\n");
|
||||||
printf(" %s -c cocoon.json\n", prog);
|
printf(" %s -c cocoon.json\n", prog);
|
||||||
@ -77,6 +78,7 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
config->max_connections = 0;
|
config->max_connections = 0;
|
||||||
config->timeout_ms = 0;
|
config->timeout_ms = 0;
|
||||||
config->log_level = LOG_LEVEL_INFO;
|
config->log_level = LOG_LEVEL_INFO;
|
||||||
|
config->gzip_enabled = true;
|
||||||
|
|
||||||
bool has_root_dir = false;
|
bool has_root_dir = false;
|
||||||
bool has_port = false;
|
bool has_port = false;
|
||||||
@ -84,6 +86,7 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
bool has_max_conn = false;
|
bool has_max_conn = false;
|
||||||
bool has_timeout = false;
|
bool has_timeout = false;
|
||||||
bool has_log_level = false;
|
bool has_log_level = false;
|
||||||
|
bool has_gzip_enabled = false;
|
||||||
const char *config_file = NULL;
|
const char *config_file = NULL;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
@ -92,7 +95,7 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
config_file = argv[i];
|
config_file = argv[i];
|
||||||
} else if (strcmp(argv[i], "-r") == 0) {
|
} else if (strcmp(argv[i], "-r") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config->root_dir = argv[i];
|
config->root_dir = strdup(argv[i]);
|
||||||
has_root_dir = true;
|
has_root_dir = true;
|
||||||
} else if (strcmp(argv[i], "-p") == 0) {
|
} else if (strcmp(argv[i], "-p") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
@ -127,6 +130,9 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
} else if (strcmp(argv[i], "-v") == 0) {
|
} else if (strcmp(argv[i], "-v") == 0) {
|
||||||
config->log_level = LOG_LEVEL_DEBUG;
|
config->log_level = LOG_LEVEL_DEBUG;
|
||||||
has_log_level = true;
|
has_log_level = true;
|
||||||
|
} else if (strcmp(argv[i], "--no-gzip") == 0) {
|
||||||
|
config->gzip_enabled = false;
|
||||||
|
has_gzip_enabled = true;
|
||||||
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
exit(0);
|
exit(0);
|
||||||
@ -144,7 +150,7 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
}
|
}
|
||||||
/* 用命令行参数覆盖配置文件 */
|
/* 用命令行参数覆盖配置文件 */
|
||||||
config_merge(config, config, has_root_dir, has_port, has_workers,
|
config_merge(config, config, has_root_dir, has_port, has_workers,
|
||||||
has_max_conn, has_timeout, has_log_level);
|
has_max_conn, has_timeout, has_log_level, has_gzip_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config->root_dir) {
|
if (!config->root_dir) {
|
||||||
|
|||||||
6
server.c
6
server.c
@ -56,6 +56,7 @@ typedef struct {
|
|||||||
uint32_t timeout_ms; /**< 连接空闲超时毫秒(从配置复制) */
|
uint32_t timeout_ms; /**< 连接空闲超时毫秒(从配置复制) */
|
||||||
coco_timer_t *timer; /**< 空闲超时定时器 */
|
coco_timer_t *timer; /**< 空闲超时定时器 */
|
||||||
coco_coro_t *coro; /**< 当前处理协程 */
|
coco_coro_t *coro; /**< 当前处理协程 */
|
||||||
|
bool gzip_enabled; /**< 是否启用 gzip 压缩 */
|
||||||
} connection_t;
|
} connection_t;
|
||||||
struct server_context {
|
struct server_context {
|
||||||
int listen_fd; /**< 监听 socket */
|
int listen_fd; /**< 监听 socket */
|
||||||
@ -427,14 +428,14 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
|
|||||||
http_request_free(&req);
|
http_request_free(&req);
|
||||||
return req.keep_alive;
|
return req.keep_alive;
|
||||||
}
|
}
|
||||||
static_serve_file(conn->fd, &index_req, root_dir);
|
static_serve_file(conn->fd, &index_req, root_dir, conn->gzip_enabled);
|
||||||
} else {
|
} else {
|
||||||
/* 无 index.html,生成目录列表 */
|
/* 无 index.html,生成目录列表 */
|
||||||
static_serve_directory(conn->fd, &req, root_dir, real_path);
|
static_serve_directory(conn->fd, &req, root_dir, real_path);
|
||||||
}
|
}
|
||||||
} else if (S_ISREG(st.st_mode)) {
|
} else if (S_ISREG(st.st_mode)) {
|
||||||
/* 普通文件 */
|
/* 普通文件 */
|
||||||
static_serve_file(conn->fd, &req, root_dir);
|
static_serve_file(conn->fd, &req, root_dir, conn->gzip_enabled);
|
||||||
} else {
|
} else {
|
||||||
static_send_error(conn->fd, 403, req.keep_alive);
|
static_send_error(conn->fd, 403, req.keep_alive);
|
||||||
}
|
}
|
||||||
@ -620,6 +621,7 @@ static void accept_loop(void *arg) {
|
|||||||
conn->closed = false;
|
conn->closed = false;
|
||||||
conn->root_dir = ctx->config.root_dir;
|
conn->root_dir = ctx->config.root_dir;
|
||||||
conn->timeout_ms = ctx->config.timeout_ms > 0 ? ctx->config.timeout_ms : CONN_TIMEOUT_MS;
|
conn->timeout_ms = ctx->config.timeout_ms > 0 ? ctx->config.timeout_ms : CONN_TIMEOUT_MS;
|
||||||
|
conn->gzip_enabled = ctx->config.gzip_enabled;
|
||||||
|
|
||||||
atomic_fetch_add(&g_active_connections, 1);
|
atomic_fetch_add(&g_active_connections, 1);
|
||||||
log_debug("新连接 fd=%d,当前活跃连接: %d", client_fd,
|
log_debug("新连接 fd=%d,当前活跃连接: %d", client_fd,
|
||||||
|
|||||||
4
static.c
4
static.c
@ -275,7 +275,7 @@ int static_send_error(int fd, int status_code, bool keep_alive) {
|
|||||||
* @param root_dir 静态资源根目录
|
* @param root_dir 静态资源根目录
|
||||||
* @return COCOON_OK 成功,负值错误码
|
* @return COCOON_OK 成功,负值错误码
|
||||||
*/
|
*/
|
||||||
int static_serve_file(int fd, const http_request_t *req, const char *root_dir) {
|
int static_serve_file(int fd, const http_request_t *req, const char *root_dir, bool gzip_enabled) {
|
||||||
char real_path[4096];
|
char real_path[4096];
|
||||||
if (!safe_path_join(real_path, sizeof(real_path), root_dir, req->path)) {
|
if (!safe_path_join(real_path, sizeof(real_path), root_dir, req->path)) {
|
||||||
return static_send_error(fd, 403, req->keep_alive);
|
return static_send_error(fd, 403, req->keep_alive);
|
||||||
@ -345,7 +345,7 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir) {
|
|||||||
char *gzip_buf = NULL;
|
char *gzip_buf = NULL;
|
||||||
ssize_t gzip_len = 0;
|
ssize_t gzip_len = 0;
|
||||||
|
|
||||||
if (req->accept_gzip && !req->has_range && req->method != HTTP_HEAD) {
|
if (gzip_enabled && req->accept_gzip && !req->has_range && req->method != HTTP_HEAD) {
|
||||||
const char *mime = http_mime_type(real_path);
|
const char *mime = http_mime_type(real_path);
|
||||||
if (is_compressible_mime(mime) && file_size > 256) {
|
if (is_compressible_mime(mime) && file_size > 256) {
|
||||||
/* 读取文件内容到内存 */
|
/* 读取文件内容到内存 */
|
||||||
|
|||||||
3
static.h
3
static.h
@ -32,9 +32,10 @@ int send_all(int fd, const char *buf, size_t len);
|
|||||||
* @param fd 客户端 socket 文件描述符
|
* @param fd 客户端 socket 文件描述符
|
||||||
* @param req HTTP 请求
|
* @param req HTTP 请求
|
||||||
* @param root_dir 静态资源根目录
|
* @param root_dir 静态资源根目录
|
||||||
|
* @param gzip_enabled 是否启用 gzip 压缩
|
||||||
* @return COCOON_OK 成功,负值错误码
|
* @return COCOON_OK 成功,负值错误码
|
||||||
*/
|
*/
|
||||||
int static_serve_file(int fd, const http_request_t *req, const char *root_dir);
|
int static_serve_file(int fd, const http_request_t *req, const char *root_dir, bool gzip_enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* static_serve_directory - 生成目录列表 HTML
|
* static_serve_directory - 生成目录列表 HTML
|
||||||
|
|||||||
@ -34,7 +34,8 @@ void test_load_valid_config(void) {
|
|||||||
" \"num_workers\": 8,\n"
|
" \"num_workers\": 8,\n"
|
||||||
" \"max_connections\": 5000,\n"
|
" \"max_connections\": 5000,\n"
|
||||||
" \"timeout_ms\": 60000,\n"
|
" \"timeout_ms\": 60000,\n"
|
||||||
" \"log_level\": \"debug\"\n"
|
" \"log_level\": \"debug\",\n"
|
||||||
|
" \"gzip_enabled\": false\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
);
|
);
|
||||||
cocoon_config_t cfg = {0};
|
cocoon_config_t cfg = {0};
|
||||||
@ -46,6 +47,7 @@ void test_load_valid_config(void) {
|
|||||||
TEST_ASSERT_EQUAL(5000, cfg.max_connections);
|
TEST_ASSERT_EQUAL(5000, cfg.max_connections);
|
||||||
TEST_ASSERT_EQUAL(60000, cfg.timeout_ms);
|
TEST_ASSERT_EQUAL(60000, cfg.timeout_ms);
|
||||||
TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, cfg.log_level);
|
TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, cfg.log_level);
|
||||||
|
TEST_ASSERT_FALSE(cfg.gzip_enabled);
|
||||||
free((void *)cfg.root_dir);
|
free((void *)cfg.root_dir);
|
||||||
cleanup(p);
|
cleanup(p);
|
||||||
}
|
}
|
||||||
@ -108,19 +110,21 @@ void test_merge_override_all(void) {
|
|||||||
.num_workers = 2,
|
.num_workers = 2,
|
||||||
.max_connections = 100,
|
.max_connections = 100,
|
||||||
.timeout_ms = 30000,
|
.timeout_ms = 30000,
|
||||||
.log_level = LOG_LEVEL_INFO
|
.log_level = LOG_LEVEL_INFO,
|
||||||
|
.gzip_enabled = true
|
||||||
};
|
};
|
||||||
cocoon_config_t cmdline = {
|
cocoon_config_t cmdline = {
|
||||||
.root_dir = "/new",
|
.root_dir = strdup("/new"),
|
||||||
.port = 9090,
|
.port = 9090,
|
||||||
.threaded = true,
|
.threaded = true,
|
||||||
.num_workers = 4,
|
.num_workers = 4,
|
||||||
.max_connections = 200,
|
.max_connections = 200,
|
||||||
.timeout_ms = 60000,
|
.timeout_ms = 60000,
|
||||||
.log_level = LOG_LEVEL_DEBUG
|
.log_level = LOG_LEVEL_DEBUG,
|
||||||
|
.gzip_enabled = false
|
||||||
};
|
};
|
||||||
config_merge(&base, &cmdline,
|
config_merge(&base, &cmdline,
|
||||||
true, true, true, true, true, true);
|
true, true, true, true, true, true, true);
|
||||||
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir);
|
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir);
|
||||||
TEST_ASSERT_EQUAL(9090, base.port);
|
TEST_ASSERT_EQUAL(9090, base.port);
|
||||||
TEST_ASSERT_TRUE(base.threaded);
|
TEST_ASSERT_TRUE(base.threaded);
|
||||||
@ -128,7 +132,9 @@ void test_merge_override_all(void) {
|
|||||||
TEST_ASSERT_EQUAL(200, base.max_connections);
|
TEST_ASSERT_EQUAL(200, base.max_connections);
|
||||||
TEST_ASSERT_EQUAL(60000, base.timeout_ms);
|
TEST_ASSERT_EQUAL(60000, base.timeout_ms);
|
||||||
TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, base.log_level);
|
TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, base.log_level);
|
||||||
|
TEST_ASSERT_FALSE(base.gzip_enabled);
|
||||||
free((void *)base.root_dir);
|
free((void *)base.root_dir);
|
||||||
|
free((void *)cmdline.root_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_merge_no_override(void) {
|
void test_merge_no_override(void) {
|
||||||
@ -146,7 +152,7 @@ void test_merge_no_override(void) {
|
|||||||
.log_level = LOG_LEVEL_DEBUG
|
.log_level = LOG_LEVEL_DEBUG
|
||||||
};
|
};
|
||||||
config_merge(&base, &cmdline,
|
config_merge(&base, &cmdline,
|
||||||
false, false, false, false, false, false);
|
false, false, false, false, false, false, false);
|
||||||
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir);
|
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir);
|
||||||
TEST_ASSERT_EQUAL(8080, base.port);
|
TEST_ASSERT_EQUAL(8080, base.port);
|
||||||
TEST_ASSERT_FALSE(base.threaded);
|
TEST_ASSERT_FALSE(base.threaded);
|
||||||
@ -169,7 +175,7 @@ void test_merge_partial_override(void) {
|
|||||||
.log_level = LOG_LEVEL_DEBUG
|
.log_level = LOG_LEVEL_DEBUG
|
||||||
};
|
};
|
||||||
config_merge(&base, &cmdline,
|
config_merge(&base, &cmdline,
|
||||||
true, false, false, true, false, false);
|
true, false, false, true, false, false, false);
|
||||||
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir); /* overridden */
|
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir); /* overridden */
|
||||||
TEST_ASSERT_EQUAL(8080, base.port); /* not overridden */
|
TEST_ASSERT_EQUAL(8080, base.port); /* not overridden */
|
||||||
TEST_ASSERT_EQUAL(2, base.num_workers); /* not overridden */
|
TEST_ASSERT_EQUAL(2, base.num_workers); /* not overridden */
|
||||||
@ -234,7 +240,7 @@ void test_merge_cmdline_null_root_dir(void) {
|
|||||||
/* cmdline root_dir 为 NULL,不应覆盖 base */
|
/* cmdline root_dir 为 NULL,不应覆盖 base */
|
||||||
cocoon_config_t base = {.root_dir = strdup("/old"), .port = 8080};
|
cocoon_config_t base = {.root_dir = strdup("/old"), .port = 8080};
|
||||||
cocoon_config_t cmdline = {.root_dir = NULL, .port = 9090};
|
cocoon_config_t cmdline = {.root_dir = NULL, .port = 9090};
|
||||||
config_merge(&base, &cmdline, true, true, false, false, false, false);
|
config_merge(&base, &cmdline, true, true, false, false, false, false, false);
|
||||||
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir); /* NULL 不覆盖 */
|
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir); /* NULL 不覆盖 */
|
||||||
TEST_ASSERT_EQUAL(9090, base.port); /* port 覆盖 */
|
TEST_ASSERT_EQUAL(9090, base.port); /* port 覆盖 */
|
||||||
free((void *)base.root_dir);
|
free((void *)base.root_dir);
|
||||||
@ -242,10 +248,36 @@ void test_merge_cmdline_null_root_dir(void) {
|
|||||||
|
|
||||||
void test_merge_null_safety(void) {
|
void test_merge_null_safety(void) {
|
||||||
/* 不应 crash */
|
/* 不应 crash */
|
||||||
config_merge(NULL, NULL, true, true, true, true, true, true);
|
config_merge(NULL, NULL, true, true, true, true, true, true, true);
|
||||||
TEST_ASSERT_TRUE(1);
|
TEST_ASSERT_TRUE(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_load_gzip_enabled(void) {
|
||||||
|
/* gzip_enabled: true */
|
||||||
|
const char *p1 = write_temp_config("{\"gzip_enabled\": true}");
|
||||||
|
cocoon_config_t cfg1 = {0};
|
||||||
|
TEST_ASSERT_TRUE(config_load_from_file(p1, &cfg1));
|
||||||
|
TEST_ASSERT_TRUE(cfg1.gzip_enabled);
|
||||||
|
free((void *)cfg1.root_dir);
|
||||||
|
cleanup(p1);
|
||||||
|
|
||||||
|
/* gzip_enabled: false */
|
||||||
|
const char *p2 = write_temp_config("{\"gzip_enabled\": false}");
|
||||||
|
cocoon_config_t cfg2 = {0};
|
||||||
|
TEST_ASSERT_TRUE(config_load_from_file(p2, &cfg2));
|
||||||
|
TEST_ASSERT_FALSE(cfg2.gzip_enabled);
|
||||||
|
free((void *)cfg2.root_dir);
|
||||||
|
cleanup(p2);
|
||||||
|
|
||||||
|
/* 默认值应为 true(在 main 中设置,但这里 0 初始化后为 false) */
|
||||||
|
const char *p3 = write_temp_config("{\"port\": 3000}");
|
||||||
|
cocoon_config_t cfg3 = {.gzip_enabled = true};
|
||||||
|
TEST_ASSERT_TRUE(config_load_from_file(p3, &cfg3));
|
||||||
|
TEST_ASSERT_TRUE(cfg3.gzip_enabled); /* 未指定时保持原值 */
|
||||||
|
free((void *)cfg3.root_dir);
|
||||||
|
cleanup(p3);
|
||||||
|
}
|
||||||
|
|
||||||
void setUp(void) {}
|
void setUp(void) {}
|
||||||
void tearDown(void) {}
|
void tearDown(void) {}
|
||||||
|
|
||||||
@ -270,5 +302,6 @@ int main(void) {
|
|||||||
RUN_TEST(test_merge_null_safety);
|
RUN_TEST(test_merge_null_safety);
|
||||||
RUN_TEST(test_merge_cmdline_null_root_dir);
|
RUN_TEST(test_merge_cmdline_null_root_dir);
|
||||||
|
|
||||||
|
RUN_TEST(test_load_gzip_enabled);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user