Some checks failed
CI / build (push) Failing after 26s
- cocoon_config_t 新增 ACME 字段(enabled, directory_url, email, domains, cert_path, key_path, renew_days),支持最多 8 个域名 - config.c: JSON 解析 acme 配置块、校验逻辑、config_merge 支持 - main.c: 新增 --acme 命令行开关 - server.c: server_start 启动 ACME 自动续期后台协程(检查周期 24h, 提前 renew_days 触发重新签发) - acme.c: 实现 acme_save_certificate 证书持久化、 acme_cert_days_until_expiry 过期天数检查 - acme.h: 新增 save_certificate / cert_days_until_expiry 接口声明 - 新增 tests/unit/test_acme_config.c:覆盖配置解析、校验、合并、 证书过期检查、证书保存共 6 项测试 - 修复 test_config.c 中 config_merge 调用以匹配新签名
310 lines
11 KiB
C
310 lines
11 KiB
C
/**
|
||
* main.c - Cocoon 入口
|
||
*
|
||
* 解析命令行参数和配置文件,初始化并启动服务器。
|
||
*
|
||
* @author xfy
|
||
*/
|
||
|
||
#include "cocoon.h"
|
||
#include "server.h"
|
||
#include "config.h"
|
||
#include "platform.h"
|
||
#include "access_log.h"
|
||
#include "plugin.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <signal.h>
|
||
|
||
/**
|
||
* g_ctx - 全局服务器上下文(用于信号处理)
|
||
*/
|
||
static server_context_t *g_ctx = NULL;
|
||
|
||
/**
|
||
* g_reload_plugins - 插件热重载标志(由 SIGUSR1 触发)
|
||
*/
|
||
static volatile int g_reload_plugins = 0;
|
||
|
||
/**
|
||
* signal_handler - 优雅关闭信号处理
|
||
*
|
||
* 捕获 SIGINT/SIGTERM,触发服务器停止。
|
||
*
|
||
* @param sig 信号编号
|
||
*/
|
||
static void signal_handler(int sig) {
|
||
(void)sig;
|
||
if (g_ctx) {
|
||
printf("\n[Cocoon] 收到关闭信号,正在优雅停止...\n");
|
||
server_stop(g_ctx);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* reload_handler - 插件热重载信号处理
|
||
*
|
||
* 捕获 SIGUSR1,设置热重载标志。
|
||
* 主循环中检测到标志后执行 cocoon_plugin_reload()。
|
||
*
|
||
* @param sig 信号编号
|
||
*/
|
||
static void reload_handler(int sig) {
|
||
(void)sig;
|
||
printf("\n[Cocoon] 收到 SIGUSR1,热重载插件...\n");
|
||
cocoon_plugin_reload();
|
||
}
|
||
|
||
static void sighup_handler(int sig) {
|
||
(void)sig;
|
||
printf("\n[Cocoon] 收到 SIGHUP,准备热重载配置...\n");
|
||
server_request_reload(g_ctx);
|
||
}
|
||
|
||
/**
|
||
* print_usage - 打印使用说明
|
||
*
|
||
* @param prog 程序名
|
||
*/
|
||
static void print_usage(const char *prog) {
|
||
printf("Usage: %s [options]\n\n", prog);
|
||
printf("Options:\n");
|
||
printf(" -c <file> JSON 配置文件路径\n");
|
||
printf(" -r <dir> 静态资源根目录(必填,或配置文件指定)\n");
|
||
printf(" -p <port> 监听端口(默认 8080)\n");
|
||
printf(" -t 启用多线程调度\n");
|
||
printf(" -w <num> 工作线程数(默认自动检测 CPU 核心)\n");
|
||
printf(" -m <num> 最大并发连接数(默认无限制)\n");
|
||
printf(" -o <ms> 连接空闲超时毫秒(默认 30000)\n");
|
||
printf(" -l <level> 日志级别: error, warn, info, debug(默认 info)\n");
|
||
printf(" -v 详细日志输出(等同于 -l debug)\n");
|
||
printf(" --cert <path> TLS 证书路径(启用 HTTPS)\n");
|
||
printf(" --key <path> TLS 私钥路径\n");
|
||
printf(" --tls 显式启用 TLS(需同时指定 --cert 和 --key)\n");
|
||
printf(" --no-gzip 禁用 gzip 压缩\n");
|
||
printf(" --access-log <path> 访问日志文件路径(- 表示 stdout)\n");
|
||
printf(" --cors 启用 CORS 支持\n");
|
||
printf(" --auth-user <user> Basic Auth 用户名\n");
|
||
printf(" --auth-pass <pass> Basic Auth 密码\n");
|
||
printf(" --rate-limit <n> 每秒最大请求数(限流)\n");
|
||
printf(" --plugin <path> 加载插件(可多次指定)\n");
|
||
printf(" --acme 启用 ACME 自动证书\n");
|
||
printf("\nSignals:\n");
|
||
printf(" SIGHUP 热重载配置文件(无需重启服务器)\n");
|
||
printf(" SIGUSR1 热重载所有插件(无需重启服务器)\n");
|
||
printf("\nExample:\n");
|
||
printf(" %s -c cocoon.json\n", prog);
|
||
printf(" %s -r ./www -p 8080\n", prog);
|
||
printf(" %s -c cocoon.json -p 9090 # 命令行覆盖配置文件的端口\n", prog);
|
||
}
|
||
|
||
/**
|
||
* parse_args - 解析命令行参数
|
||
*
|
||
* 支持配置文件 + 命令行覆盖的混合模式。
|
||
*
|
||
* @param argc 参数个数
|
||
* @param argv 参数数组
|
||
* @param config 输出配置结构体
|
||
* @return true 成功,false 参数错误
|
||
*/
|
||
static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
||
/* 默认值 */
|
||
memset(config, 0, sizeof(*config));
|
||
config->port = 8080;
|
||
config->log_level = LOG_LEVEL_INFO;
|
||
config->gzip_enabled = true;
|
||
config->brotli_enabled = true;
|
||
|
||
/* 第一阶段:查找配置文件路径 */
|
||
const char *config_file = NULL;
|
||
for (int i = 1; i < argc; i++) {
|
||
if (strcmp(argv[i], "-c") == 0) {
|
||
if (++i >= argc) return false;
|
||
config_file = argv[i];
|
||
}
|
||
}
|
||
|
||
/* 第二阶段:加载配置文件(如果有) */
|
||
if (config_file) {
|
||
if (!config_load_from_file(config_file, config)) {
|
||
fprintf(stderr, "Error: 无法加载配置文件: %s\n", config_file);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/* 第三阶段:解析命令行参数,覆盖配置文件 */
|
||
for (int i = 1; i < argc; i++) {
|
||
if (strcmp(argv[i], "-c") == 0) {
|
||
i++; /* 已处理,跳过 */
|
||
} else if (strcmp(argv[i], "-r") == 0) {
|
||
if (++i >= argc) return false;
|
||
free((void *)config->root_dir);
|
||
config->root_dir = strdup(argv[i]);
|
||
} else if (strcmp(argv[i], "-p") == 0) {
|
||
if (++i >= argc) return false;
|
||
config->port = (uint16_t)atoi(argv[i]);
|
||
if (config->port == 0) config->port = 8080;
|
||
} else if (strcmp(argv[i], "-t") == 0) {
|
||
config->threaded = true;
|
||
} else if (strcmp(argv[i], "-w") == 0) {
|
||
if (++i >= argc) return false;
|
||
config->num_workers = (uint32_t)atoi(argv[i]);
|
||
} else if (strcmp(argv[i], "-m") == 0) {
|
||
if (++i >= argc) return false;
|
||
config->max_connections = (uint32_t)atoi(argv[i]);
|
||
} else if (strcmp(argv[i], "-o") == 0) {
|
||
if (++i >= argc) return false;
|
||
config->timeout_ms = (uint32_t)atoi(argv[i]);
|
||
} else if (strcmp(argv[i], "-l") == 0) {
|
||
if (++i >= argc) return false;
|
||
if (strcmp(argv[i], "error") == 0) config->log_level = LOG_LEVEL_ERROR;
|
||
else if (strcmp(argv[i], "warn") == 0) config->log_level = LOG_LEVEL_WARN;
|
||
else if (strcmp(argv[i], "info") == 0) config->log_level = LOG_LEVEL_INFO;
|
||
else if (strcmp(argv[i], "debug") == 0) config->log_level = LOG_LEVEL_DEBUG;
|
||
else {
|
||
fprintf(stderr, "Unknown log level: %s\n", argv[i]);
|
||
return false;
|
||
}
|
||
} else if (strcmp(argv[i], "-v") == 0) {
|
||
config->log_level = LOG_LEVEL_DEBUG;
|
||
} else if (strcmp(argv[i], "--no-gzip") == 0) {
|
||
config->gzip_enabled = false;
|
||
} else if (strcmp(argv[i], "--no-brotli") == 0) {
|
||
config->brotli_enabled = false;
|
||
} else if (strcmp(argv[i], "--cert") == 0) {
|
||
if (++i >= argc) return false;
|
||
free((void *)config->tls_cert);
|
||
config->tls_cert = strdup(argv[i]);
|
||
} else if (strcmp(argv[i], "--key") == 0) {
|
||
if (++i >= argc) return false;
|
||
free((void *)config->tls_key);
|
||
config->tls_key = strdup(argv[i]);
|
||
} else if (strcmp(argv[i], "--tls") == 0) {
|
||
config->tls_enabled = true;
|
||
} else if (strcmp(argv[i], "--access-log") == 0) {
|
||
if (++i >= argc) return false;
|
||
free((void *)config->access_log_path);
|
||
config->access_log_path = strdup(argv[i]);
|
||
} else if (strcmp(argv[i], "--cors") == 0) {
|
||
config->cors_enabled = true;
|
||
} else if (strcmp(argv[i], "--auth-user") == 0) {
|
||
if (++i >= argc) return false;
|
||
free((void *)config->auth_user);
|
||
config->auth_user = strdup(argv[i]);
|
||
} else if (strcmp(argv[i], "--auth-pass") == 0) {
|
||
if (++i >= argc) return false;
|
||
free((void *)config->auth_pass);
|
||
config->auth_pass = strdup(argv[i]);
|
||
} else if (strcmp(argv[i], "--rate-limit") == 0) {
|
||
if (++i >= argc) return false;
|
||
config->rate_limit = (uint32_t)atoi(argv[i]);
|
||
} else if (strcmp(argv[i], "--plugin") == 0) {
|
||
if (++i >= argc) return false;
|
||
if (config->num_plugins < COCOON_MAX_PLUGINS) {
|
||
config->plugins[config->num_plugins++] = strdup(argv[i]);
|
||
} else {
|
||
fprintf(stderr, "Error: 最多支持 %d 个插件\n", COCOON_MAX_PLUGINS);
|
||
return false;
|
||
}
|
||
} else if (strcmp(argv[i], "--acme") == 0) {
|
||
config->acme_enabled = true;
|
||
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||
print_usage(argv[0]);
|
||
exit(0);
|
||
} else {
|
||
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (!config->root_dir) {
|
||
fprintf(stderr, "Error: 必须指定静态资源根目录(-r <dir> 或配置文件)\n");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* main - 程序入口
|
||
*
|
||
* 解析参数,注册信号,创建并启动服务器。
|
||
*
|
||
* @param argc 参数个数
|
||
* @param argv 参数数组
|
||
* @return 0 成功,1 失败
|
||
*/
|
||
int main(int argc, char *argv[]) {
|
||
cocoon_config_t config = {0};
|
||
const char *config_file_path = NULL;
|
||
|
||
if (!parse_args(argc, argv, &config)) {
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
|
||
/* 从参数中查找配置文件路径 */
|
||
for (int i = 1; i < argc; i++) {
|
||
if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) {
|
||
config_file_path = argv[i + 1];
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* 初始化 socket 子系统(Windows 下 WSAStartup) */
|
||
if (cocoon_socket_init() != 0) {
|
||
fprintf(stderr, "[Cocoon] socket 子系统初始化失败\n");
|
||
return 1;
|
||
}
|
||
|
||
/* 注册信号处理 */
|
||
cocoon_signal_setup(signal_handler);
|
||
signal(SIGUSR1, reload_handler);
|
||
signal(SIGHUP, sighup_handler);
|
||
signal(SIGPIPE, SIG_IGN); /* 忽略 SIGPIPE,防止写入已关闭的连接时进程终止 */
|
||
|
||
/* 设置日志级别 */
|
||
log_set_level(config.log_level);
|
||
|
||
/* 初始化访问日志 */
|
||
if (config.access_log_path) {
|
||
access_log_init(config.access_log_path);
|
||
}
|
||
|
||
/* 创建服务器 */
|
||
g_ctx = server_create(&config, config_file_path);
|
||
if (!g_ctx) {
|
||
fprintf(stderr, "[Cocoon] 创建服务器失败\n");
|
||
return 1;
|
||
}
|
||
|
||
/* 启动(阻塞) */
|
||
int ret = server_start(g_ctx);
|
||
|
||
/* 清理 */
|
||
server_destroy(g_ctx);
|
||
g_ctx = NULL;
|
||
|
||
/* 释放 socket 子系统(Windows 下 WSACleanup) */
|
||
cocoon_socket_cleanup();
|
||
|
||
/* 关闭访问日志 */
|
||
access_log_close();
|
||
|
||
/* 释放配置文件分配的内存 */
|
||
/* 释放插件路径 */
|
||
for (size_t i = 0; i < config.num_plugins; i++) {
|
||
free((void *)config.plugins[i]);
|
||
}
|
||
if (config.root_dir) free((void *)config.root_dir);
|
||
if (config.tls_cert) free((void *)config.tls_cert);
|
||
if (config.tls_key) free((void *)config.tls_key);
|
||
if (config.access_log_path) free((void *)config.access_log_path);
|
||
if (config.auth_user) free((void *)config.auth_user);
|
||
if (config.auth_pass) free((void *)config.auth_pass);
|
||
|
||
return ret == COCOON_OK ? 0 : 1;
|
||
}
|