feat(config): 插件配置支持 JSON 数组格式
- 极简 JSON 解析器新增 '[' / ']' token 支持 - plugins 字段可同时兼容字符串和字符串数组 - 向后兼容:单字符串 "plugins.so" 仍有效 - 向前扩展:数组 ["a.so", "b.so"] 支持多插件 修复: - .gitignore 添加遗漏的 test_websocket 二进制 - Makefile 补全 test_log 单元测试编译规则 - cocoon.json 示例改为数组格式 - test_config.c 新增 3 项 plugins 解析测试(字符串/单数组/多数组) 全部 142 个单元测试 + 68 项集成测试通过 ✓
This commit is contained in:
parent
f2231756a3
commit
2dc29d0687
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ tests/unit/test_log
|
|||||||
tests/unit/test_multipart
|
tests/unit/test_multipart
|
||||||
tests/unit/test_static
|
tests/unit/test_static
|
||||||
tests/unit/test_server
|
tests/unit/test_server
|
||||||
|
tests/unit/test_websocket
|
||||||
tests/fixtures/uploads/
|
tests/fixtures/uploads/
|
||||||
plugins/*.so
|
plugins/*.so
|
||||||
www/uploads/
|
www/uploads/
|
||||||
|
|||||||
3
Makefile
3
Makefile
@ -98,6 +98,9 @@ $(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c
|
|||||||
$(UNIT_TEST_DIR)/test_websocket: $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c $(UNITY_SRC)
|
$(UNIT_TEST_DIR)/test_websocket: $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c $(UNITY_SRC)
|
||||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c $(UNITY_SRC) $(LDFLAGS)
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c $(UNITY_SRC) $(LDFLAGS)
|
||||||
|
|
||||||
|
$(UNIT_TEST_DIR)/test_log: $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC)
|
||||||
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) -lm
|
||||||
|
|
||||||
$(UNIT_TEST_DIR)/test_config: $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC)
|
$(UNIT_TEST_DIR)/test_config: $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC)
|
||||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC) -lm
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC) -lm
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,6 @@
|
|||||||
"log_level": "info",
|
"log_level": "info",
|
||||||
"gzip_enabled": true,
|
"gzip_enabled": true,
|
||||||
"brotli_enabled": true,
|
"brotli_enabled": true,
|
||||||
"plugins": "plugins/hello.so",
|
"plugins": ["plugins/hello.so"],
|
||||||
"access_log": "-"
|
"access_log": "-"
|
||||||
}
|
}
|
||||||
|
|||||||
31
config.c
31
config.c
@ -28,6 +28,8 @@ typedef enum {
|
|||||||
TOKEN_NUMBER, /* 123 */
|
TOKEN_NUMBER, /* 123 */
|
||||||
TOKEN_TRUE, /* true */
|
TOKEN_TRUE, /* true */
|
||||||
TOKEN_FALSE, /* false */
|
TOKEN_FALSE, /* false */
|
||||||
|
TOKEN_LBRACKET, /* [ */
|
||||||
|
TOKEN_RBRACKET, /* ] */
|
||||||
TOKEN_COMMA, /* , */
|
TOKEN_COMMA, /* , */
|
||||||
TOKEN_COLON, /* : */
|
TOKEN_COLON, /* : */
|
||||||
TOKEN_INVALID /* 错误 */
|
TOKEN_INVALID /* 错误 */
|
||||||
@ -89,6 +91,8 @@ static token_t parser_next_token(parser_t *p) {
|
|||||||
switch (c) {
|
switch (c) {
|
||||||
case '{': p->pos++; t.type = TOKEN_LBRACE; return t;
|
case '{': p->pos++; t.type = TOKEN_LBRACE; return t;
|
||||||
case '}': p->pos++; t.type = TOKEN_RBRACE; return t;
|
case '}': p->pos++; t.type = TOKEN_RBRACE; return t;
|
||||||
|
case '[': p->pos++; t.type = TOKEN_LBRACKET; return t;
|
||||||
|
case ']': p->pos++; t.type = TOKEN_RBRACKET; return t;
|
||||||
case ',': p->pos++; t.type = TOKEN_COMMA; return t;
|
case ',': p->pos++; t.type = TOKEN_COMMA; return t;
|
||||||
case ':': p->pos++; t.type = TOKEN_COLON; return t;
|
case ':': p->pos++; t.type = TOKEN_COLON; return t;
|
||||||
case '"': {
|
case '"': {
|
||||||
@ -313,13 +317,36 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
|
|||||||
} else if (strcmp(key_str, "rate_limit") == 0 && val.type == TOKEN_NUMBER) {
|
} else if (strcmp(key_str, "rate_limit") == 0 && val.type == TOKEN_NUMBER) {
|
||||||
long v = token_to_long(&val);
|
long v = token_to_long(&val);
|
||||||
if (v >= 0 && v < 1000000) config->rate_limit = (uint32_t)v;
|
if (v >= 0 && v < 1000000) config->rate_limit = (uint32_t)v;
|
||||||
} else if (strcmp(key_str, "plugins") == 0 && val.type == TOKEN_STRING) {
|
} else if (strcmp(key_str, "plugins") == 0) {
|
||||||
|
if (val.type == TOKEN_STRING) {
|
||||||
char *v = token_str_dup(&val);
|
char *v = token_str_dup(&val);
|
||||||
if (v && config->num_plugins < COCOON_MAX_PLUGINS) {
|
if (v && config->num_plugins < COCOON_MAX_PLUGINS) {
|
||||||
config->plugins[config->num_plugins++] = v;
|
config->plugins[config->num_plugins++] = v;
|
||||||
}
|
}
|
||||||
|
} else if (val.type == TOKEN_LBRACKET) {
|
||||||
|
/* 解析字符串数组 */
|
||||||
|
while (1) {
|
||||||
|
token_t item = parser_next_token(&p);
|
||||||
|
if (item.type == TOKEN_RBRACKET) break;
|
||||||
|
if (item.type == TOKEN_STRING) {
|
||||||
|
char *v = token_str_dup(&item);
|
||||||
|
if (v && config->num_plugins < COCOON_MAX_PLUGINS) {
|
||||||
|
config->plugins[config->num_plugins++] = v;
|
||||||
}
|
}
|
||||||
/* 其他字段:忽略(未来扩展预留) */
|
} else {
|
||||||
|
fprintf(stderr, "[Config] 第 %d 行: plugins 数组期望字符串项\n", item.line);
|
||||||
|
}
|
||||||
|
token_t sep = parser_next_token(&p);
|
||||||
|
if (sep.type == TOKEN_RBRACKET) break;
|
||||||
|
if (sep.type != TOKEN_COMMA) {
|
||||||
|
fprintf(stderr, "[Config] 第 %d 行: plugins 数组期望 ',' 或 ']'\n", sep.line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "[Config] 第 %d 行: plugins 期望字符串或数组\n", val.line);
|
||||||
|
}
|
||||||
|
} /* 其他字段:忽略(未来扩展预留) */
|
||||||
|
|
||||||
free(key_str);
|
free(key_str);
|
||||||
|
|
||||||
|
|||||||
@ -312,6 +312,55 @@ void test_load_brotli_enabled(void) {
|
|||||||
cleanup(p3);
|
cleanup(p3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_load_plugins_string(void) {
|
||||||
|
const char *p = write_temp_config(
|
||||||
|
"{\n"
|
||||||
|
" \"port\": 3000,\n"
|
||||||
|
" \"plugins\": \"plugins/hello.so\"\n"
|
||||||
|
"}\n"
|
||||||
|
);
|
||||||
|
cocoon_config_t cfg = {0};
|
||||||
|
TEST_ASSERT_TRUE(config_load_from_file(p, &cfg));
|
||||||
|
TEST_ASSERT_EQUAL(1, cfg.num_plugins);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("plugins/hello.so", cfg.plugins[0]);
|
||||||
|
free((void *)cfg.root_dir);
|
||||||
|
for (size_t i = 0; i < cfg.num_plugins; i++) free((void *)cfg.plugins[i]);
|
||||||
|
cleanup(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_load_plugins_array(void) {
|
||||||
|
const char *p = write_temp_config(
|
||||||
|
"{\n"
|
||||||
|
" \"port\": 3000,\n"
|
||||||
|
" \"plugins\": [\"plugins/a.so\"]\n"
|
||||||
|
"}\n"
|
||||||
|
);
|
||||||
|
cocoon_config_t cfg = {0};
|
||||||
|
TEST_ASSERT_TRUE(config_load_from_file(p, &cfg));
|
||||||
|
TEST_ASSERT_EQUAL(1, cfg.num_plugins);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("plugins/a.so", cfg.plugins[0]);
|
||||||
|
free((void *)cfg.root_dir);
|
||||||
|
for (size_t i = 0; i < cfg.num_plugins; i++) free((void *)cfg.plugins[i]);
|
||||||
|
cleanup(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_load_plugins_array_multiple(void) {
|
||||||
|
const char *p = write_temp_config(
|
||||||
|
"{\n"
|
||||||
|
" \"port\": 3000,\n"
|
||||||
|
" \"plugins\": [\"plugins/a.so\", \"plugins/b.so\"]\n"
|
||||||
|
"}\n"
|
||||||
|
);
|
||||||
|
cocoon_config_t cfg = {0};
|
||||||
|
TEST_ASSERT_TRUE(config_load_from_file(p, &cfg));
|
||||||
|
TEST_ASSERT_EQUAL(2, cfg.num_plugins);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("plugins/a.so", cfg.plugins[0]);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("plugins/b.so", cfg.plugins[1]);
|
||||||
|
free((void *)cfg.root_dir);
|
||||||
|
for (size_t i = 0; i < cfg.num_plugins; i++) free((void *)cfg.plugins[i]);
|
||||||
|
cleanup(p);
|
||||||
|
}
|
||||||
|
|
||||||
void setUp(void) {}
|
void setUp(void) {}
|
||||||
void tearDown(void) {}
|
void tearDown(void) {}
|
||||||
|
|
||||||
@ -338,5 +387,8 @@ int main(void) {
|
|||||||
|
|
||||||
RUN_TEST(test_load_gzip_enabled);
|
RUN_TEST(test_load_gzip_enabled);
|
||||||
RUN_TEST(test_load_brotli_enabled);
|
RUN_TEST(test_load_brotli_enabled);
|
||||||
|
RUN_TEST(test_load_plugins_string);
|
||||||
|
RUN_TEST(test_load_plugins_array);
|
||||||
|
RUN_TEST(test_load_plugins_array_multiple);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user