cocoon/main.c
xfy911 f38effff49
Some checks failed
CI / build (push) Failing after 26s
feat: ACME 配置集成 + 自动续期后台协程
- 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 调用以匹配新签名
2026-06-16 12:45:08 +08:00

310 lines
11 KiB
C
Raw Permalink 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.

/**
* 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;
}