cocoon/config.c
xfy911 60ef9ed908 feat(vhost): 虚拟主机/多站点支持
- cocoon.h: 新增 cocoon_vhost_t 结构体和 COCOON_MAX_VHOSTS 宏
- config.c: JSON 解析器新增 vhosts 数组解析
- server.c: HTTP/1.1 请求根据 Host 头匹配虚拟主机 root_dir
- http2.h/c: HTTP/2 根据 :authority 匹配虚拟主机,存储为 Host 头
- http2.c: 新增 http2_session_set_server_config() API
- cocoon.json: 添加 vhosts 配置示例
- tests: 新增 3 项虚拟主机集成测试
- 编译零警告,142 单元测试 + 106 集成测试全部通过
2026-06-09 15:28:57 +08:00

669 lines
28 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* config.c - JSON 配置文件解析实现
*
* 极简 JSON 解析器,只处理 cocoon 配置所需的字段:
* - 字符串root_dir, log_level
* - 整数port, num_workers, max_connections, timeout_ms
* - 布尔值threaded
*
* 保持零依赖,不引入外部 JSON 库。
*
* @author xfy
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
/* === 内部:极简 JSON token 类型 === */
typedef enum {
TOKEN_EOF,
TOKEN_LBRACE, /* { */
TOKEN_RBRACE, /* } */
TOKEN_STRING, /* "..." */
TOKEN_NUMBER, /* 123 */
TOKEN_TRUE, /* true */
TOKEN_FALSE, /* false */
TOKEN_LBRACKET, /* [ */
TOKEN_RBRACKET, /* ] */
TOKEN_COMMA, /* , */
TOKEN_COLON, /* : */
TOKEN_INVALID /* 错误 */
} token_type_t;
typedef struct {
token_type_t type;
const char *start;
size_t len;
int line;
} token_t;
typedef struct {
const char *src;
size_t pos;
size_t len;
int line;
} parser_t;
/* === 内部parser 辅助函数 === */
/**
* parser_init - 初始化 JSON 解析器
*
* @param p 解析器状态结构
* @param src JSON 源字符串
* @param len 源字符串长度
*/
static void parser_init(parser_t *p, const char *src, size_t len) {
p->src = src;
p->pos = 0;
p->len = len;
p->line = 1;
}
/**
* parser_skip_ws - 跳过空白字符和注释
*
* 支持空格、制表符、换行,以及 // 行注释。
*
* @param p 解析器状态结构
*/
static void parser_skip_ws(parser_t *p) {
while (p->pos < p->len) {
char c = p->src[p->pos];
if (c == ' ' || c == '\t' || c == '\r') {
p->pos++;
} else if (c == '\n') {
p->pos++;
p->line++;
} else if (c == '/' && p->pos + 1 < p->len && p->src[p->pos + 1] == '/') {
/* 跳过 // 注释 */
p->pos += 2;
while (p->pos < p->len && p->src[p->pos] != '\n') p->pos++;
} else {
break;
}
}
}
/**
* parser_next_token - 读取下一个 JSON token
*
* 支持的 token 类型字符串、数字、true、false、
* 以及各种分隔符({ } [ ] , :)。
*
* @param p 解析器状态结构
* @return 下一个 token
*/
static token_t parser_next_token(parser_t *p) {
parser_skip_ws(p);
token_t t = {TOKEN_INVALID, NULL, 0, p->line};
if (p->pos >= p->len) {
t.type = TOKEN_EOF;
return t;
}
const char *start = p->src + p->pos;
char c = start[0];
switch (c) {
case '{': p->pos++; t.type = TOKEN_LBRACE; 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_COLON; return t;
case '"': {
/* 字符串 */
p->pos++; /* skip " */
t.start = p->src + p->pos;
while (p->pos < p->len && p->src[p->pos] != '"') {
if (p->src[p->pos] == '\\' && p->pos + 1 < p->len) {
p->pos += 2; /* skip escaped */
} else {
p->pos++;
}
}
t.len = (size_t)(p->src + p->pos - t.start);
if (p->pos < p->len) p->pos++; /* skip closing " */
t.type = TOKEN_STRING;
return t;
}
default: {
if (isdigit(c) || c == '-') {
/* 数字 */
t.start = start;
p->pos++;
while (p->pos < p->len && (isdigit(p->src[p->pos]) || p->src[p->pos] == '.')) {
p->pos++;
}
t.len = (size_t)(p->src + p->pos - t.start);
t.type = TOKEN_NUMBER;
return t;
} else if (strncmp(start, "true", 4) == 0) {
p->pos += 4;
t.type = TOKEN_TRUE;
return t;
} else if (strncmp(start, "false", 5) == 0) {
p->pos += 5;
t.type = TOKEN_FALSE;
return t;
}
/* 未知 token */
p->pos++;
t.type = TOKEN_INVALID;
return t;
}
}
}
/**
* token_expect - 期望下一个 token 为指定类型
*
* @param p 解析器状态结构
* @param expected 期望的 token 类型
* @return true 匹配false 不匹配
*/
static bool token_expect(parser_t *p, token_type_t expected) {
token_t t = parser_next_token(p);
return t.type == expected;
}
/**
* token_str_dup - 将字符串 token 复制为 C 字符串
*
* 处理 JSON 字符串中的转义序列(\n \t \r \\ \")。
*
* @param t 字符串 token
* @return 新分配的 C 字符串,调用者负责释放;失败返回 NULL
*/
static char *token_str_dup(const token_t *t) {
char *buf = (char *)malloc(t->len + 1);
if (!buf) return NULL;
size_t j = 0;
for (size_t i = 0; i < t->len; i++) {
if (t->start[i] == '\\' && i + 1 < t->len) {
char next = t->start[i + 1];
switch (next) {
case 'n': buf[j++] = '\n'; i++; break;
case 't': buf[j++] = '\t'; i++; break;
case 'r': buf[j++] = '\r'; i++; break;
case '\\': buf[j++] = '\\'; i++; break;
case '"': buf[j++] = '"'; i++; break;
default: buf[j++] = t->start[i]; break;
}
} else {
buf[j++] = t->start[i];
}
}
buf[j] = '\0';
return buf;
}
/**
* token_to_long - 将数字 token 转换为长整型
*
* @param t 数字 token
* @return 转换后的数值
*/
static long token_to_long(const token_t *t) {
char buf[32] = {0};
size_t n = t->len < 31 ? t->len : 31;
memcpy(buf, t->start, n);
return strtol(buf, NULL, 10);
}
/**
* str_to_log_level - 将字符串转换为日志级别
*
* @param str 日志级别字符串error/warn/info/debug
* @return 对应的日志级别枚举值,无效时返回 LOG_LEVEL_INFO
*/
static log_level_t str_to_log_level(const char *str) {
if (strcmp(str, "error") == 0) return LOG_LEVEL_ERROR;
if (strcmp(str, "warn") == 0) return LOG_LEVEL_WARN;
if (strcmp(str, "info") == 0) return LOG_LEVEL_INFO;
if (strcmp(str, "debug") == 0) return LOG_LEVEL_DEBUG;
return LOG_LEVEL_INFO; /* 默认 */
}
/* === 公共 API === */
/**
* config_load_from_file - 从 JSON 配置文件加载配置
*
* 解析 cocoon.json 格式,支持以下字段:
* root_dir, port, threaded, num_workers, max_connections, timeout_ms,
* log_level, gzip_enabled, brotli_enabled, tls_cert, tls_key, tls_enabled,
* access_log, cors_enabled, auth_user, auth_pass, rate_limit,
* plugins字符串或数组, proxies对象数组
*
* 未知字段将被静默忽略,便于向后兼容。
*
* @param path 配置文件路径
* @param config 输出配置结构体
* @return true 成功false 失败(会输出错误信息到 stderr
*/
bool config_load_from_file(const char *path, cocoon_config_t *config) {
if (!path || !config) return false;
FILE *fp = fopen(path, "r");
if (!fp) {
fprintf(stderr, "[Config] 无法打开配置文件: %s (%s)\n", path, strerror(errno));
return false;
}
/* 读取文件内容 */
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (size <= 0 || size > 65536) { /* 限制 64KB */
fclose(fp);
fprintf(stderr, "[Config] 配置文件过大或为空\n");
return false;
}
char *buf = (char *)malloc((size_t)size + 1);
if (!buf) {
fclose(fp);
return false;
}
size_t read = fread(buf, 1, (size_t)size, fp);
fclose(fp);
buf[read] = '\0';
parser_t p;
parser_init(&p, buf, read);
/* 期望 { */
if (!token_expect(&p, TOKEN_LBRACE)) {
fprintf(stderr, "[Config] 配置文件格式错误: 期望 '{'\n");
free(buf);
return false;
}
/* 解析键值对 */
while (1) {
token_t key = parser_next_token(&p);
if (key.type == TOKEN_RBRACE) break; /* 空对象 {} */
if (key.type != TOKEN_STRING) {
fprintf(stderr, "[Config] 第 %d 行: 期望字符串键\n", key.line);
free(buf);
return false;
}
if (!token_expect(&p, TOKEN_COLON)) {
fprintf(stderr, "[Config] 第 %d 行: 键后缺少 ':'\n", key.line);
free(buf);
return false;
}
token_t val = parser_next_token(&p);
char *key_str = token_str_dup(&key);
if (strcmp(key_str, "root_dir") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
free((void *)config->root_dir); /* 释放旧的 */
config->root_dir = v;
}
} else if (strcmp(key_str, "port") == 0 && val.type == TOKEN_NUMBER) {
long v = token_to_long(&val);
if (v > 0 && v < 65536) config->port = (uint16_t)v;
} else if (strcmp(key_str, "threaded") == 0) {
if (val.type == TOKEN_TRUE) config->threaded = true;
else if (val.type == TOKEN_FALSE) config->threaded = false;
} else if (strcmp(key_str, "num_workers") == 0 && val.type == TOKEN_NUMBER) {
long v = token_to_long(&val);
if (v > 0 && v < 1024) config->num_workers = (uint32_t)v;
} else if (strcmp(key_str, "max_connections") == 0 && val.type == TOKEN_NUMBER) {
long v = token_to_long(&val);
if (v >= 0 && v < 1000000) config->max_connections = (uint32_t)v;
} else if (strcmp(key_str, "timeout_ms") == 0 && val.type == TOKEN_NUMBER) {
long v = token_to_long(&val);
if (v >= 0 && v < 3600000) config->timeout_ms = (uint32_t)v;
} else if (strcmp(key_str, "log_level") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
config->log_level = str_to_log_level(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;
} else if (strcmp(key_str, "brotli_enabled") == 0) {
if (val.type == TOKEN_TRUE) config->brotli_enabled = true;
else if (val.type == TOKEN_FALSE) config->brotli_enabled = false;
} else if (strcmp(key_str, "tls_cert") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
free((void *)config->tls_cert);
config->tls_cert = v;
}
} else if (strcmp(key_str, "tls_key") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
free((void *)config->tls_key);
config->tls_key = v;
}
} else if (strcmp(key_str, "tls_enabled") == 0) {
if (val.type == TOKEN_TRUE) config->tls_enabled = true;
else if (val.type == TOKEN_FALSE) config->tls_enabled = false;
} else if (strcmp(key_str, "access_log") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
free((void *)config->access_log_path);
config->access_log_path = v;
}
} else if (strcmp(key_str, "cors_enabled") == 0) {
if (val.type == TOKEN_TRUE) config->cors_enabled = true;
else if (val.type == TOKEN_FALSE) config->cors_enabled = false;
} else if (strcmp(key_str, "auth_user") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
free((void *)config->auth_user);
config->auth_user = v;
}
} else if (strcmp(key_str, "auth_pass") == 0 && val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v) {
free((void *)config->auth_pass);
config->auth_pass = v;
}
} else if (strcmp(key_str, "rate_limit") == 0 && val.type == TOKEN_NUMBER) {
long v = token_to_long(&val);
if (v >= 0 && v < 1000000) config->rate_limit = (uint32_t)v;
} else if (strcmp(key_str, "plugins") == 0) {
if (val.type == TOKEN_STRING) {
char *v = token_str_dup(&val);
if (v && config->num_plugins < COCOON_MAX_PLUGINS) {
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);
}
} else if (strcmp(key_str, "proxies") == 0) {
if (val.type == TOKEN_LBRACKET) {
/* 解析对象数组 */
while (1) {
token_t item = parser_next_token(&p);
if (item.type == TOKEN_RBRACKET) break;
if (item.type == TOKEN_LBRACE) {
char prefix[256] = {0};
char target[256] = {0};
/* 解析对象内的键值对 */
while (1) {
token_t pkey = parser_next_token(&p);
if (pkey.type == TOKEN_RBRACE) break;
if (pkey.type != TOKEN_STRING) {
fprintf(stderr, "[Config] 第 %d 行: proxy 对象期望字符串键\n", pkey.line);
break;
}
if (!token_expect(&p, TOKEN_COLON)) break;
token_t pval = parser_next_token(&p);
char *pk = token_str_dup(&pkey);
if (strcmp(pk, "prefix") == 0 && pval.type == TOKEN_STRING) {
char *v = token_str_dup(&pval);
if (v) { strncpy(prefix, v, sizeof(prefix)-1); free(v); }
} else if (strcmp(pk, "target") == 0 && pval.type == TOKEN_STRING) {
char *v = token_str_dup(&pval);
if (v) { strncpy(target, v, sizeof(target)-1); free(v); }
} else if (strcmp(pk, "pool_size") == 0 && pval.type == TOKEN_NUMBER) {
char *v = token_str_dup(&pval);
if (v) { config->proxies[config->num_proxies].pool_size = (uint32_t)atoi(v); free(v); }
} else if (strcmp(pk, "weight") == 0 && pval.type == TOKEN_NUMBER) {
char *v = token_str_dup(&pval);
if (v) { config->proxies[config->num_proxies].weight = (uint32_t)atoi(v); free(v); }
} else if (strcmp(pk, "healthcheck") == 0 && pval.type == TOKEN_LBRACE) {
/* 解析 healthcheck 子对象 */
while (1) {
token_t hc_key = parser_next_token(&p);
if (hc_key.type == TOKEN_RBRACE) break;
if (hc_key.type != TOKEN_STRING) {
token_t skip_sep = parser_next_token(&p);
if (skip_sep.type == TOKEN_RBRACE) break;
continue;
}
if (!token_expect(&p, TOKEN_COLON)) break;
token_t hc_val = parser_next_token(&p);
char *hk = token_str_dup(&hc_key);
if (strcmp(hk, "path") == 0 && hc_val.type == TOKEN_STRING) {
char *v = token_str_dup(&hc_val);
if (v) { strncpy(config->proxies[config->num_proxies].healthcheck.path, v, sizeof(config->proxies[0].healthcheck.path)-1); free(v); }
} else if (strcmp(hk, "interval_ms") == 0 && hc_val.type == TOKEN_NUMBER) {
config->proxies[config->num_proxies].healthcheck.interval_ms = (uint32_t)token_to_long(&hc_val);
} else if (strcmp(hk, "timeout_ms") == 0 && hc_val.type == TOKEN_NUMBER) {
config->proxies[config->num_proxies].healthcheck.timeout_ms = (uint32_t)token_to_long(&hc_val);
} else if (strcmp(hk, "enabled") == 0) {
if (hc_val.type == TOKEN_TRUE) config->proxies[config->num_proxies].healthcheck.enabled = true;
else if (hc_val.type == TOKEN_FALSE) config->proxies[config->num_proxies].healthcheck.enabled = false;
}
free(hk);
token_t hc_sep = parser_next_token(&p);
if (hc_sep.type == TOKEN_RBRACE) break;
if (hc_sep.type != TOKEN_COMMA) break;
}
}
free(pk);
token_t psep = parser_next_token(&p);
if (psep.type == TOKEN_RBRACE) break;
if (psep.type != TOKEN_COMMA) {
fprintf(stderr, "[Config] 第 %d 行: proxy 对象期望 ',' 或 '}'\n", psep.line);
break;
}
}
if (prefix[0] && target[0] && config->num_proxies < COCOON_MAX_PROXY_RULES) {
size_t prefix_len = strlen(prefix);
if (prefix_len >= sizeof(config->proxies[0].prefix)) prefix_len = sizeof(config->proxies[0].prefix) - 1;
memcpy(config->proxies[config->num_proxies].prefix, prefix, prefix_len);
config->proxies[config->num_proxies].prefix[prefix_len] = '\0';
size_t target_len = strlen(target);
if (target_len >= sizeof(config->proxies[0].target)) target_len = sizeof(config->proxies[0].target) - 1;
memcpy(config->proxies[config->num_proxies].target, target, target_len);
config->proxies[config->num_proxies].target[target_len] = '\0';
if (config->proxies[config->num_proxies].pool_size == 0) {
config->proxies[config->num_proxies].pool_size = 4; /* 默认连接池大小 */
}
config->num_proxies++;
}
} else {
fprintf(stderr, "[Config] 第 %d 行: proxies 数组期望对象项\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 行: proxies 数组期望 ',' 或 ']'\n", sep.line);
break;
}
}
} else {
fprintf(stderr, "[Config] 第 %d 行: proxies 期望数组\n", val.line);
}
} else if (strcmp(key_str, "vhosts") == 0) {
if (val.type == TOKEN_LBRACKET) {
/* 解析虚拟主机数组 */
while (1) {
token_t item = parser_next_token(&p);
if (item.type == TOKEN_RBRACKET) break;
if (item.type == TOKEN_LBRACE) {
char server_name[256] = {0};
char root_dir[512] = {0};
/* 解析对象内的键值对 */
while (1) {
token_t vkey = parser_next_token(&p);
if (vkey.type == TOKEN_RBRACE) break;
if (vkey.type != TOKEN_STRING) {
fprintf(stderr, "[Config] 第 %d 行: vhost 对象期望字符串键\n", vkey.line);
break;
}
if (!token_expect(&p, TOKEN_COLON)) break;
token_t vval = parser_next_token(&p);
char *vk = token_str_dup(&vkey);
if (strcmp(vk, "server_name") == 0 && vval.type == TOKEN_STRING) {
char *v = token_str_dup(&vval);
if (v) { strncpy(server_name, v, sizeof(server_name)-1); free(v); }
} else if (strcmp(vk, "root_dir") == 0 && vval.type == TOKEN_STRING) {
char *v = token_str_dup(&vval);
if (v) { strncpy(root_dir, v, sizeof(root_dir)-1); free(v); }
}
free(vk);
token_t vsep = parser_next_token(&p);
if (vsep.type == TOKEN_RBRACE) break;
if (vsep.type != TOKEN_COMMA) {
fprintf(stderr, "[Config] 第 %d 行: vhost 对象期望 ',' 或 '}'\n", vsep.line);
break;
}
}
if (server_name[0] && root_dir[0] && config->num_vhosts < COCOON_MAX_VHOSTS) {
size_t sn_len = strlen(server_name);
if (sn_len >= sizeof(config->vhosts[0].server_name)) sn_len = sizeof(config->vhosts[0].server_name) - 1;
memcpy(config->vhosts[config->num_vhosts].server_name, server_name, sn_len);
config->vhosts[config->num_vhosts].server_name[sn_len] = '\0';
size_t rd_len = strlen(root_dir);
if (rd_len >= sizeof(config->vhosts[0].root_dir)) rd_len = sizeof(config->vhosts[0].root_dir) - 1;
memcpy(config->vhosts[config->num_vhosts].root_dir, root_dir, rd_len);
config->vhosts[config->num_vhosts].root_dir[rd_len] = '\0';
config->num_vhosts++;
}
} else {
fprintf(stderr, "[Config] 第 %d 行: vhosts 数组期望对象项\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 行: vhosts 数组期望 ',' 或 ']'\n", sep.line);
break;
}
}
} else {
fprintf(stderr, "[Config] 第 %d 行: vhosts 期望数组\n", val.line);
}
} /* 其他字段:忽略(未来扩展预留) */
free(key_str);
/* 检查逗号或右括号 */
token_t sep = parser_next_token(&p);
if (sep.type == TOKEN_RBRACE) break;
if (sep.type != TOKEN_COMMA) {
fprintf(stderr, "[Config] 第 %d 行: 期望 ',' 或 '}'\n", sep.line);
free(buf);
return false;
}
}
free(buf);
return true;
}
/**
* config_merge - 将命令行参数合并到基础配置
*
* 命令行显式指定的值覆盖配置文件中的值。
* 对于字符串字段,会释放旧值并复制新值。
*
* @param base 基础配置(通常来自配置文件)
* @param cmdline 命令行配置
* @param has_root_dir 是否显式指定 root_dir
* @param has_port 是否显式指定 port
* @param has_workers 是否显式指定 num_workers
* @param has_max_conn 是否显式指定 max_connections
* @param has_timeout 是否显式指定 timeout_ms
* @param has_log_level 是否显式指定 log_level
* @param has_gzip_enabled 是否显式指定 gzip_enabled
* @param has_brotli_enabled 是否显式指定 brotli_enabled
* @param has_tls_cert 是否显式指定 tls_cert
* @param has_tls_key 是否显式指定 tls_key
* @param has_tls_enabled 是否显式指定 tls_enabled
* @param has_access_log 是否显式指定 access_log
* @param has_cors_enabled 是否显式指定 cors_enabled
* @param has_auth_user 是否显式指定 auth_user
* @param has_auth_pass 是否显式指定 auth_pass
* @param has_rate_limit 是否显式指定 rate_limit
* @param has_plugins 是否显式指定 plugins
*/
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
bool has_root_dir, bool has_port, bool has_workers,
bool has_max_conn, bool has_timeout, bool has_log_level,
bool has_gzip_enabled, bool has_brotli_enabled,
bool has_tls_cert, bool has_tls_key, bool has_tls_enabled,
bool has_access_log,
bool has_cors_enabled, bool has_auth_user, bool has_auth_pass,
bool has_rate_limit,
bool has_plugins) {
if (!base || !cmdline) return;
/* 命令行显式指定的值覆盖配置文件 */
if (has_root_dir && cmdline->root_dir) {
free((void *)base->root_dir);
base->root_dir = strdup(cmdline->root_dir);
}
if (has_port) base->port = cmdline->port;
if (has_workers) base->num_workers = cmdline->num_workers;
if (has_max_conn) base->max_connections = cmdline->max_connections;
if (has_timeout) base->timeout_ms = cmdline->timeout_ms;
if (has_log_level) base->log_level = cmdline->log_level;
if (has_gzip_enabled) base->gzip_enabled = cmdline->gzip_enabled;
if (has_brotli_enabled) base->brotli_enabled = cmdline->brotli_enabled;
if (has_tls_cert && cmdline->tls_cert) {
free((void *)base->tls_cert);
base->tls_cert = strdup(cmdline->tls_cert);
}
if (has_tls_key && cmdline->tls_key) {
free((void *)base->tls_key);
base->tls_key = strdup(cmdline->tls_key);
}
if (has_tls_enabled) base->tls_enabled = cmdline->tls_enabled;
if (has_access_log && cmdline->access_log_path) {
free((void *)base->access_log_path);
base->access_log_path = strdup(cmdline->access_log_path);
}
if (has_cors_enabled) base->cors_enabled = cmdline->cors_enabled;
if (has_auth_user && cmdline->auth_user) {
free((void *)base->auth_user);
base->auth_user = strdup(cmdline->auth_user);
}
if (has_auth_pass && cmdline->auth_pass) {
free((void *)base->auth_pass);
base->auth_pass = strdup(cmdline->auth_pass);
}
if (has_rate_limit) base->rate_limit = cmdline->rate_limit;
if (has_plugins) {
for (size_t i = 0; i < cmdline->num_plugins && i < COCOON_MAX_PLUGINS; i++) {
if (base->num_plugins < COCOON_MAX_PLUGINS) {
base->plugins[base->num_plugins++] = strdup(cmdline->plugins[i]);
}
}
}
/* threaded 是 flag 参数,命令行指定了就用命令行的 */
if (cmdline->threaded) base->threaded = true;
}