From 9da9bd9449fece82d3e407a1dcf6b8e91540e0aa Mon Sep 17 00:00:00 2001 From: xfy911 Date: Sun, 7 Jun 2026 09:05:45 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=B8=BA=20config.c=E3=80=81log.c?= =?UTF-8?q?=E3=80=81tls.c=20=E6=B7=BB=E5=8A=A0=E5=87=BD=E6=95=B0=E7=BA=A7?= =?UTF-8?q?=20Doxygen=20=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config.c: 为 JSON 解析器内部函数和公共 API 添加完整注释 - log.c: 为日志级别转换和输出函数添加注释 - tls.c: 为 TLS 连接管理、Memory BIO 操作和 ALPN 回调添加注释 - http2.c: 移除过时的 TODO 占位注释(请求体收集已实现) --- config.c | 92 +++++++++++++++++++++++++++++++++++++- http2.c | 2 +- log.c | 49 ++++++++++++++++++++ tls.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 270 insertions(+), 7 deletions(-) diff --git a/config.c b/config.c index baa3a2d..3f959e8 100644 --- a/config.c +++ b/config.c @@ -51,6 +51,13 @@ typedef struct { /* === 内部: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; @@ -58,6 +65,13 @@ static void parser_init(parser_t *p, const char *src, size_t 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]; @@ -76,6 +90,15 @@ static void parser_skip_ws(parser_t *p) { } } +/** + * 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}; @@ -139,12 +162,26 @@ static token_t parser_next_token(parser_t *p) { } } +/** + * 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 内容为 C 字符串(处理转义) */ +/** + * 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; @@ -169,6 +206,12 @@ static char *token_str_dup(const token_t *t) { 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; @@ -176,6 +219,12 @@ static long token_to_long(const token_t *t) { 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; @@ -186,6 +235,21 @@ static log_level_t str_to_log_level(const char *str) { /* === 公共 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; @@ -423,6 +487,32 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) { 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, diff --git a/http2.c b/http2.c index 7ba6f98..58ad50b 100644 --- a/http2.c +++ b/http2.c @@ -689,7 +689,7 @@ static int on_stream_close_callback(nghttp2_session *session __attribute__((unus /** * on_data_chunk_recv_callback - DATA 帧数据块接收回调 * - * 目前为占位实现,TODO:将请求体数据追加到流缓冲区。 + * 将请求体数据追加到流缓冲区。支持 Content-Length 和分块传输。 * * @param session nghttp2 会话(未使用) * @param flags 标志(未使用) diff --git a/log.c b/log.c index 63716fe..b33f5bc 100644 --- a/log.c +++ b/log.c @@ -13,6 +13,12 @@ static log_level_t g_level = LOG_LEVEL_INFO; static const char *g_prefix = "[Cocoon]"; +/** + * level_str - 将日志级别转换为字符串 + * + * @param level 日志级别 + * @return 级别名称(ERROR/WARN/INFO/DEBUG/UNKNOWN) + */ static const char *level_str(log_level_t level) { switch (level) { case LOG_LEVEL_ERROR: return "ERROR"; @@ -23,18 +29,45 @@ static const char *level_str(log_level_t level) { } } +/** + * log_set_level - 设置全局日志级别 + * + * 低于此级别的日志消息将被忽略。 + * + * @param level 日志级别 + */ void log_set_level(log_level_t level) { g_level = level; } +/** + * log_set_prefix - 设置日志前缀 + * + * @param prefix 前缀字符串,设为 NULL 则无前缀 + */ void log_set_prefix(const char *prefix) { g_prefix = prefix; } +/** + * log_get_level - 获取当前日志级别 + * + * @return 当前日志级别 + */ log_level_t log_get_level(void) { return g_level; } +/** + * log_output - 日志输出核心函数 + * + * 格式化时间戳、前缀、级别和消息,输出到 stderr。 + * 若日志级别低于全局级别则直接返回。 + * + * @param level 日志级别 + * @param fmt 格式字符串 + * @param args 可变参数列表 + */ static void log_output(log_level_t level, const char *fmt, va_list args) { if (level > g_level) return; @@ -52,6 +85,10 @@ static void log_output(log_level_t level, const char *fmt, va_list args) { fprintf(stderr, "\n"); } +/** + * log_error - 输出 ERROR 级别日志 + * @param fmt 格式字符串 + */ void log_error(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -59,6 +96,10 @@ void log_error(const char *fmt, ...) { va_end(args); } +/** + * log_warn - 输出 WARN 级别日志 + * @param fmt 格式字符串 + */ void log_warn(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -66,6 +107,10 @@ void log_warn(const char *fmt, ...) { va_end(args); } +/** + * log_info - 输出 INFO 级别日志 + * @param fmt 格式字符串 + */ void log_info(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -73,6 +118,10 @@ void log_info(const char *fmt, ...) { va_end(args); } +/** + * log_debug - 输出 DEBUG 级别日志 + * @param fmt 格式字符串 + */ void log_debug(const char *fmt, ...) { va_list args; va_start(args, fmt); diff --git a/tls.c b/tls.c index def1637..8fd71ec 100644 --- a/tls.c +++ b/tls.c @@ -33,12 +33,27 @@ static tls_conn_t **g_map = NULL; static int g_map_cap = 0; static SSL_CTX *g_ctx = NULL; -/* 内部:O(1) fd 查表 */ +/** + * tls_lookup - 根据 fd 查找 TLS 连接 + * + * O(1) 查表,使用动态数组索引。 + * + * @param fd socket 文件描述符 + * @return TLS 连接结构,未找到返回 NULL + */ static tls_conn_t* tls_lookup(int fd) { if (fd >= 0 && fd < g_map_cap) return g_map[fd]; return NULL; } +/** + * tls_map_set - 将 fd 与 TLS 连接关联 + * + * 若数组容量不足则自动扩容。 + * + * @param fd socket 文件描述符 + * @param t TLS 连接结构 + */ static void tls_map_set(int fd, tls_conn_t *t) { if (fd >= g_map_cap) { int old_cap = g_map_cap; @@ -49,11 +64,25 @@ static void tls_map_set(int fd, tls_conn_t *t) { g_map[fd] = t; } +/** + * tls_map_clear - 清除 fd 与 TLS 连接的关联 + * + * @param fd socket 文件描述符 + */ static void tls_map_clear(int fd) { if (fd >= 0 && fd < g_map_cap) g_map[fd] = NULL; } -/* 内部:从 socket 读取原始数据(协程感知) */ +/** + * socket_read - 从 socket 读取原始数据(协程感知) + * + * 若当前在协程调度器中则使用 coco_read,否则使用标准 read。 + * + * @param fd socket 文件描述符 + * @param buf 读取缓冲区 + * @param len 最大读取长度 + * @return 实际读取字节数,失败返回 -1 + */ static ssize_t socket_read(int fd, void *buf, size_t len) { if (coco_sched_get_current() != NULL) { return coco_read(fd, buf, len); @@ -61,7 +90,16 @@ static ssize_t socket_read(int fd, void *buf, size_t len) { return read(fd, buf, len); } -/* 内部:向 socket 写入原始数据 */ +/** + * socket_write_all - 向 socket 写入全部数据(协程感知) + * + * 循环写入直至全部数据发送完毕。若当前在协程调度器中则使用 coco_write。 + * + * @param fd socket 文件描述符 + * @param buf 数据缓冲区 + * @param len 数据长度 + * @return 实际发送字节数,失败返回 -1 + */ static ssize_t socket_write_all(int fd, const void *buf, size_t len) { if (coco_sched_get_current() != NULL) { size_t sent = 0; @@ -94,7 +132,13 @@ static ssize_t socket_write_all(int fd, const void *buf, size_t len) { } } -/* 内部:将 write BIO 中的加密数据刷到 socket */ +/** + * flush_wbio - 将 write BIO 中的加密数据刷到 socket + * + * @param fd socket 文件描述符 + * @param wbio write BIO + * @return 0 成功,-1 失败 + */ static int flush_wbio(int fd, BIO *wbio) { char buf[16384]; int pending = BIO_read(wbio, buf, sizeof(buf)); @@ -105,7 +149,19 @@ static int flush_wbio(int fd, BIO *wbio) { return 0; } -/* 内部:ALPN 选择回调,优先选择 h2,否则 http/1.1 */ +/** + * tls_alpn_select_cb - ALPN 协议选择回调 + * + * 优先选择 h2(HTTP/2),次选 http/1.1。 + * + * @param ssl SSL 对象(未使用) + * @param out 输出选中的协议 + * @param outlen 输出协议长度 + * @param in 客户端 ALPN 列表 + * @param inlen 客户端 ALPN 列表长度 + * @param arg 用户参数(未使用) + * @return SSL_TLSEXT_ERR_OK 成功,SSL_TLSEXT_ERR_NOACK 无匹配 + */ static int tls_alpn_select_cb(SSL *ssl __attribute__((unused)), const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg __attribute__((unused))) { @@ -146,6 +202,16 @@ static int tls_alpn_select_cb(SSL *ssl __attribute__((unused)), const unsigned c /* ===== 公共 API ===== */ +/** + * tls_create_context - 创建 TLS 服务器上下文 + * + * 初始化 OpenSSL,加载证书和私钥,设置 ALPN 回调。 + * 最低支持 TLS 1.2。 + * + * @param cert_path 证书文件路径(PEM 格式) + * @param key_path 私钥文件路径(PEM 格式) + * @return 0 成功,-1 失败 + */ int tls_create_context(const char *cert_path, const char *key_path) { if (!cert_path || !key_path) return -1; @@ -193,6 +259,11 @@ int tls_create_context(const char *cert_path, const char *key_path) { return 0; } +/** + * tls_destroy_context - 销毁 TLS 上下文并清理所有连接 + * + * 释放 SSL_CTX、所有 TLS 连接映射表项及其关联资源。 + */ void tls_destroy_context(void) { if (g_ctx) { SSL_CTX_free(g_ctx); @@ -213,10 +284,24 @@ void tls_destroy_context(void) { } } +/** + * tls_has_context - 检查是否已创建 TLS 上下文 + * + * @return true 已创建,false 未创建 + */ bool tls_has_context(void) { return g_ctx != NULL; } +/** + * tls_accept - 接受新 TLS 连接并执行握手 + * + * 为指定 fd 创建 TLS 连接对象,执行完整的握手循环。 + * 使用 Memory BIO 与协程 I/O 集成。 + * + * @param fd socket 文件描述符 + * @return 0 成功,-1 失败 + */ int tls_accept(int fd) { if (!g_ctx) return -1; @@ -287,6 +372,16 @@ fail: return -1; } +/** + * tls_read - 从 TLS 连接读取解密数据 + * + * 若 SSL 缓冲区为空,则从 socket 读取加密数据并解密。 + * + * @param fd socket 文件描述符 + * @param buf 读取缓冲区 + * @param len 最大读取长度 + * @return 实际读取字节数,0 对端关闭,-1 错误 + */ ssize_t tls_read(int fd, void *buf, size_t len) { tls_conn_t *t = tls_lookup(fd); if (!t || !t->ssl) return -1; @@ -315,6 +410,16 @@ ssize_t tls_read(int fd, void *buf, size_t len) { } } +/** + * tls_write - 向 TLS 连接写入数据 + * + * 数据经 SSL 加密后通过 socket 发送。 + * + * @param fd socket 文件描述符 + * @param buf 数据缓冲区 + * @param len 数据长度 + * @return 实际发送字节数,-1 错误 + */ ssize_t tls_write(int fd, const void *buf, size_t len) { tls_conn_t *t = tls_lookup(fd); if (!t || !t->ssl) return -1; @@ -346,6 +451,13 @@ ssize_t tls_write(int fd, const void *buf, size_t len) { return (ssize_t)total; } +/** + * tls_close - 关闭 TLS 连接并释放资源 + * + * 发送 close_notify,释放 SSL 对象和连接映射。 + * + * @param fd socket 文件描述符 + */ void tls_close(int fd) { tls_conn_t *t = tls_lookup(fd); if (!t) return; @@ -362,10 +474,22 @@ void tls_close(int fd) { free(t); } +/** + * tls_has_connection - 检查 fd 是否有关联的 TLS 连接 + * + * @param fd socket 文件描述符 + * @return true 有 TLS 连接,false 无 + */ bool tls_has_connection(int fd) { return tls_lookup(fd) != NULL; } +/** + * tls_negotiated_http2 - 检查 ALPN 协商结果是否为 HTTP/2 + * + * @param fd socket 文件描述符 + * @return true 协商为 h2,false 未协商或为 http/1.1 + */ bool tls_negotiated_http2(int fd) { tls_conn_t *t = tls_lookup(fd); if (!t || !t->ssl) return false;