feat(middleware): 实现中间件框架(CORS / Basic Auth / Rate Limit)

- 新增 middleware.c/middleware.h:注册表 + 链式执行机制
  - 内置 CORS 中间件:支持预检请求、跨域头
  - 内置 Basic Auth 中间件:HTTP 基础认证
  - 内置 Rate Limit 中间件:基于 IP 的秒级限流
- 修复 config_merge 参数不匹配(新增 has_cors_enabled/has_auth_user/has_auth_pass/has_rate_limit)
- 修复 server_destroy 与 main() 双重释放字符串指针
- 修复 main.c 启动段错误(access_log_path 未初始化)
- 修复 rate_limit 哈希函数包含端口导致同一 IP 不同 bucket 的问题
- 修复 rate_limit 中间件 user_data 悬空指针(改为 ctx->mw_config)
- 修复 401/429 响应 Content-Length 与 body 长度不匹配导致 curl 挂起
- 修复集成测试服务器启动循环:使用 nc 代替 curl,避免触发限流计数
- 修复集成测试 kill_server:使用 kill -9 确保进程退出,并检查端口释放
- 单元测试 18/18 通过,集成测试 66/66 通过
This commit is contained in:
xfy911 2026-06-06 00:32:22 +08:00
parent e542890e26
commit dd41464b9c
15 changed files with 732 additions and 15 deletions

View File

@ -28,6 +28,11 @@ set(COCOON_SOURCES
config.c
multipart.c
platform.c
tls.c
http2.c
access_log.c
websocket.c
middleware.c
)
#

View File

@ -20,7 +20,7 @@ PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
# 源文件
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c
OBJS = $(SRCS:.c=.o)
TARGET = cocoon
@ -83,8 +83,8 @@ unit-test: $(UNIT_TEST_BINS)
fi
# 单元测试编译规则
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c $(UNITY_SRC)
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c $(UNITY_SRC) $(LDFLAGS)
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c $(UNITY_SRC)
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c $(UNITY_SRC) $(LDFLAGS)
$(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC)
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) -lm

View File

@ -42,6 +42,11 @@ typedef struct cocoon_config {
const char *tls_cert; /**< TLS 证书路径 */
const char *tls_key; /**< TLS 私钥路径 */
const char *access_log_path; /**< 访问日志文件路径NULL 或 "-" 表示 stdout */
/* 中间件配置 */
bool cors_enabled; /**< 是否启用 CORS 支持 */
const char *auth_user; /**< Basic Auth 用户名 */
const char *auth_pass; /**< Basic Auth 密码 */
uint32_t rate_limit; /**< 每秒最大请求数0 表示禁用) */
} cocoon_config_t;
/* === 服务器生命周期 API === */

View File

@ -295,6 +295,24 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
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;
}
/* 其他字段:忽略(未来扩展预留) */
@ -319,7 +337,9 @@ void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
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_access_log,
bool has_cors_enabled, bool has_auth_user, bool has_auth_pass,
bool has_rate_limit) {
if (!base || !cmdline) return;
/* 命令行显式指定的值覆盖配置文件 */
@ -347,6 +367,16 @@ void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
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;
/* threaded 是 flag 参数,命令行指定了就用命令行的 */
if (cmdline->threaded) base->threaded = true;
}

View File

@ -45,6 +45,8 @@ void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
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_access_log,
bool has_cors_enabled, bool has_auth_user, bool has_auth_pass,
bool has_rate_limit);
#endif /* COCOON_CONFIG_H */

8
http.c
View File

@ -289,6 +289,14 @@ int http_format_response_header(char *buf, size_t buf_size, const http_response_
}
}
if (resp->cors_origin && resp->cors_origin[0]) {
if ((size_t)n < buf_size) {
n += snprintf(buf + n, buf_size - (size_t)n, "Access-Control-Allow-Origin: %s\r\n", resp->cors_origin);
} else {
n += (int)strlen(resp->cors_origin) + 28;
}
}
/* 连接头 + 空行 */
if ((size_t)n < buf_size) {
n += snprintf(buf + n, buf_size - (size_t)n,

3
http.h
View File

@ -86,6 +86,9 @@ typedef struct {
/* 压缩 */
const char *content_encoding;
/* CORS */
const char *cors_origin;
} http_response_t;
/* === API === */

37
main.c
View File

@ -57,6 +57,10 @@ static void print_usage(const char *prog) {
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(" -h 显示此帮助\n");
printf("\nExample:\n");
printf(" %s -c cocoon.json\n", prog);
@ -89,6 +93,12 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
config->tls_cert = NULL;
config->tls_key = NULL;
config->tls_enabled = false;
config->access_log_path = NULL;
config->cors_enabled = false;
config->auth_user = NULL;
config->auth_pass = NULL;
config->rate_limit = 0;
bool has_root_dir = false;
bool has_port = false;
@ -102,6 +112,10 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
bool has_tls_key = false;
bool has_tls_enabled = false;
bool has_access_log = false;
bool has_cors_enabled = false;
bool has_auth_user = false;
bool has_auth_pass = false;
bool has_rate_limit = false;
const char *config_file = NULL;
for (int i = 1; i < argc; i++) {
@ -166,6 +180,21 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
if (++i >= argc) return false;
config->access_log_path = strdup(argv[i]);
has_access_log = true;
} else if (strcmp(argv[i], "--cors") == 0) {
config->cors_enabled = true;
has_cors_enabled = true;
} else if (strcmp(argv[i], "--auth-user") == 0) {
if (++i >= argc) return false;
config->auth_user = strdup(argv[i]);
has_auth_user = true;
} else if (strcmp(argv[i], "--auth-pass") == 0) {
if (++i >= argc) return false;
config->auth_pass = strdup(argv[i]);
has_auth_pass = true;
} else if (strcmp(argv[i], "--rate-limit") == 0) {
if (++i >= argc) return false;
config->rate_limit = (uint32_t)atoi(argv[i]);
has_rate_limit = true;
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
exit(0);
@ -186,7 +215,9 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
has_max_conn, has_timeout, has_log_level,
has_gzip_enabled, has_brotli_enabled,
has_tls_cert, has_tls_key, has_tls_enabled,
has_access_log);
has_access_log,
has_cors_enabled, has_auth_user, has_auth_pass,
has_rate_limit);
}
if (!config->root_dir) {
@ -207,7 +238,7 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
* @return 0 1
*/
int main(int argc, char *argv[]) {
cocoon_config_t config;
cocoon_config_t config = {0};
if (!parse_args(argc, argv, &config)) {
print_usage(argv[0]);
@ -256,6 +287,8 @@ int main(int argc, char *argv[]) {
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;
}

352
middleware.c Normal file
View File

@ -0,0 +1,352 @@
/**
* middleware.c -
*
*
* 使
*
* @author xfy
*/
#include "middleware.h"
#include "static.h"
#include "log.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <pthread.h>
/* === 注册表 === */
#define MAX_MIDDLEWARE 16
typedef struct {
char name[32];
cocoon_middleware_func_t func;
void *user_data;
bool active;
} middleware_entry_t;
static middleware_entry_t g_registry[MAX_MIDDLEWARE];
static int g_count = 0;
/* === 限流哈希表 === */
#define RATE_LIMIT_BUCKETS 256
typedef struct rate_limit_node {
struct sockaddr_storage addr;
time_t last_update;
uint32_t count;
struct rate_limit_node *next;
} rate_limit_node_t;
static rate_limit_node_t *g_rate_limit_buckets[RATE_LIMIT_BUCKETS];
static pthread_mutex_t g_rate_limit_mutex = PTHREAD_MUTEX_INITIALIZER;
/**
* rate_limit_hash - sockaddr
*/
static uint32_t rate_limit_hash(const struct sockaddr_storage *addr) {
uint32_t h = 0;
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *a4 = (const struct sockaddr_in *)addr;
const uint8_t *p = (const uint8_t *)&a4->sin_addr;
for (size_t i = 0; i < 4; i++) {
h = h * 31 + p[i];
}
} else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)addr;
const uint8_t *p = (const uint8_t *)&a6->sin6_addr;
for (size_t i = 0; i < 16; i++) {
h = h * 31 + p[i];
}
}
return h % RATE_LIMIT_BUCKETS;
}
/**
* rate_limit_addr_eq -
*/
static bool rate_limit_addr_eq(const struct sockaddr_storage *a, const struct sockaddr_storage *b) {
if (a->ss_family != b->ss_family) return false;
if (a->ss_family == AF_INET) {
const struct sockaddr_in *a4 = (const struct sockaddr_in *)a;
const struct sockaddr_in *b4 = (const struct sockaddr_in *)b;
return a4->sin_addr.s_addr == b4->sin_addr.s_addr;
}
if (a->ss_family == AF_INET6) {
const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)a;
const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *)b;
return memcmp(&a6->sin6_addr, &b6->sin6_addr, 16) == 0;
}
return false;
}
/**
* rate_limit_get_addr - socket
*/
static bool rate_limit_get_addr(cocoon_socket_t fd, struct sockaddr_storage *addr) {
socklen_t len = sizeof(*addr);
return getpeername(fd, (struct sockaddr *)addr, &len) == 0;
}
/**
* rate_limit_check -
*
* @param fd socket
* @param limit
* @return true false
*/
static bool rate_limit_check(cocoon_socket_t fd, uint32_t limit) {
if (limit == 0) return true;
struct sockaddr_storage addr;
if (!rate_limit_get_addr(fd, &addr)) return true;
uint32_t h = rate_limit_hash(&addr);
time_t now = time(NULL);
bool allow = true;
pthread_mutex_lock(&g_rate_limit_mutex);
rate_limit_node_t *node = g_rate_limit_buckets[h];
while (node) {
if (rate_limit_addr_eq(&node->addr, &addr)) {
if (now - node->last_update >= 1) {
node->last_update = now;
node->count = 1;
log_debug("Rate Limit 重置: 同一 IP 新秒");
} else {
node->count++;
log_debug("Rate Limit 计数: %u/%u", node->count, limit);
if (node->count > limit) {
allow = false;
}
}
break;
}
node = node->next;
}
if (!node) {
node = (rate_limit_node_t *)malloc(sizeof(rate_limit_node_t));
if (node) {
node->addr = addr;
node->last_update = now;
node->count = 1;
node->next = g_rate_limit_buckets[h];
g_rate_limit_buckets[h] = node;
log_debug("Rate Limit 新节点: bucket=%u", h);
}
}
pthread_mutex_unlock(&g_rate_limit_mutex);
return allow;
}
/* === Base64 解码(内部实现) === */
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int base64_decode(const char *in, char *out, int out_size) {
int val = 0, valb = -8;
int out_len = 0;
for (const char *p = in; *p; p++) {
const char *pos = strchr(base64_chars, *p);
if (!pos) {
if (*p == '=') break;
continue;
}
int c = (int)(pos - base64_chars);
val = (val << 6) + c;
valb += 6;
if (valb >= 0) {
if (out_len < out_size - 1) {
out[out_len++] = (char)((val >> valb) & 0xFF);
}
valb -= 8;
}
}
out[out_len] = '\0';
return out_len;
}
/* === 注册表 API === */
int cocoon_middleware_register(const char *name, cocoon_middleware_func_t func, void *user_data) {
if (!name || !func || g_count >= MAX_MIDDLEWARE) return -1;
for (int i = 0; i < g_count; i++) {
if (g_registry[i].active && strcmp(g_registry[i].name, name) == 0) {
/* 已存在,更新 */
g_registry[i].func = func;
g_registry[i].user_data = user_data;
return 0;
}
}
if (g_count < MAX_MIDDLEWARE) {
strncpy(g_registry[g_count].name, name, sizeof(g_registry[g_count].name) - 1);
g_registry[g_count].name[sizeof(g_registry[g_count].name) - 1] = '\0';
g_registry[g_count].func = func;
g_registry[g_count].user_data = user_data;
g_registry[g_count].active = true;
g_count++;
log_info("中间件已注册: %s", name);
return 0;
}
return -1;
}
int cocoon_middleware_unregister(const char *name) {
if (!name) return -1;
for (int i = 0; i < g_count; i++) {
if (g_registry[i].active && strcmp(g_registry[i].name, name) == 0) {
g_registry[i].active = false;
/* 压缩数组 */
for (int j = i; j < g_count - 1; j++) {
g_registry[j] = g_registry[j + 1];
}
g_count--;
return 0;
}
}
return -1;
}
int cocoon_middleware_run(http_request_t *req, cocoon_socket_t fd) {
for (int i = 0; i < g_count; i++) {
if (!g_registry[i].active) continue;
int ret = g_registry[i].func(req, fd, g_registry[i].user_data);
if (ret != 0) {
log_debug("中间件 %s 短路了请求", g_registry[i].name);
return ret;
}
}
return 0;
}
void cocoon_middleware_cleanup(void) {
g_count = 0;
memset(g_registry, 0, sizeof(g_registry));
pthread_mutex_lock(&g_rate_limit_mutex);
for (int i = 0; i < RATE_LIMIT_BUCKETS; i++) {
rate_limit_node_t *node = g_rate_limit_buckets[i];
while (node) {
rate_limit_node_t *next = node->next;
free(node);
node = next;
}
g_rate_limit_buckets[i] = NULL;
}
pthread_mutex_unlock(&g_rate_limit_mutex);
}
/* === 内置中间件 === */
int cocoon_middleware_cors(http_request_t *req, cocoon_socket_t fd, void *user_data) {
(void)user_data;
if (req->method == HTTP_OPTIONS) {
char response[512];
int n = snprintf(response, sizeof(response),
"HTTP/1.1 204 No Content\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS\r\n"
"Access-Control-Allow-Headers: Content-Type, Authorization\r\n"
"Access-Control-Max-Age: 86400\r\n"
"Connection: %s\r\n"
"Server: Cocoon/1.0\r\n"
"\r\n",
req->keep_alive ? "keep-alive" : "close");
cocoon_socket_send(fd, response, (size_t)n);
log_debug("CORS 中间件: 响应 OPTIONS 预检请求");
return 1; /* 短路 */
}
return 0; /* 继续 */
}
int cocoon_middleware_basic_auth(http_request_t *req, cocoon_socket_t fd, void *user_data) {
const cocoon_middleware_config_t *cfg = (const cocoon_middleware_config_t *)user_data;
if (!cfg || !cfg->auth_user || !cfg->auth_pass) return 0; /* 未配置,跳过 */
const char *auth_header = NULL;
for (int i = 0; i < req->num_headers; i++) {
if (strcasecmp(req->headers[i].name, "authorization") == 0) {
auth_header = req->headers[i].value;
break;
}
}
bool valid = false;
if (auth_header && strncasecmp(auth_header, "Basic ", 6) == 0) {
char decoded[256];
int decoded_len = base64_decode(auth_header + 6, decoded, sizeof(decoded));
if (decoded_len > 0) {
char *colon = strchr(decoded, ':');
if (colon) {
*colon = '\0';
valid = (strcmp(decoded, cfg->auth_user) == 0 &&
strcmp(colon + 1, cfg->auth_pass) == 0);
}
}
}
if (!valid) {
char response[512];
int n = snprintf(response, sizeof(response),
"HTTP/1.1 401 Unauthorized\r\n"
"WWW-Authenticate: Basic realm=\"Cocoon\"\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: 41\r\n"
"Connection: %s\r\n"
"Server: Cocoon/1.0\r\n"
"\r\n"
"<html><body>401 Unauthorized</body></html>",
req->keep_alive ? "keep-alive" : "close");
cocoon_socket_send(fd, response, (size_t)n);
log_warn("Basic Auth 失败: %s %s", http_method_str(req->method), req->path);
return 1; /* 短路 */
}
return 0; /* 认证通过,继续 */
}
int cocoon_middleware_rate_limit(http_request_t *req, cocoon_socket_t fd, void *user_data) {
const cocoon_middleware_config_t *cfg = (const cocoon_middleware_config_t *)user_data;
if (!cfg || cfg->rate_limit == 0) return 0; /* 未配置,跳过 */
if (!rate_limit_check(fd, cfg->rate_limit)) {
char response[512];
int n = snprintf(response, sizeof(response),
"HTTP/1.1 429 Too Many Requests\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: 48\r\n"
"Retry-After: 1\r\n"
"Connection: %s\r\n"
"Server: Cocoon/1.0\r\n"
"\r\n"
"<html><body>429 Too Many Requests</body></html>\r\n",
req->keep_alive ? "keep-alive" : "close");
cocoon_socket_send(fd, response, (size_t)n);
log_warn("Rate Limit 触发: %s %s", http_method_str(req->method), req->path);
return 1; /* 短路 */
}
return 0; /* 未限流,继续 */
}
void cocoon_middleware_init_builtin(const cocoon_middleware_config_t *config) {
if (!config) return;
if (config->cors_enabled) {
cocoon_middleware_register("cors", cocoon_middleware_cors, NULL);
}
if (config->auth_user && config->auth_pass) {
cocoon_middleware_register("basic_auth", cocoon_middleware_basic_auth, (void *)config);
}
if (config->rate_limit > 0) {
cocoon_middleware_register("rate_limit", cocoon_middleware_rate_limit, (void *)config);
}
}

142
middleware.h Normal file
View File

@ -0,0 +1,142 @@
/**
* middleware.h -
*
* hook
*
*
*
* - CORS OPTIONS
* - Basic AuthHTTP Basic
* - Rate Limit IP
*
* @author xfy
*/
#ifndef COCOON_MIDDLEWARE_H
#define COCOON_MIDDLEWARE_H
#include "http.h"
#include "platform.h"
#include <stdbool.h>
#include <stdint.h>
/* === 中间件配置 === */
/**
* cocoon_middleware_config_t -
*
* cocoon.json
*/
typedef struct {
bool cors_enabled; /**< 是否启用 CORS 支持 */
const char *auth_user; /**< Basic Auth 用户名NULL 表示禁用) */
const char *auth_pass; /**< Basic Auth 密码 */
uint32_t rate_limit; /**< 每秒最大请求数0 表示禁用限流) */
} cocoon_middleware_config_t;
/* === 中间件函数类型 === */
/**
* cocoon_middleware_func_t -
*
* HTTP socket
* 0
* 0
*
* @param req HTTP
* @param fd socket
* @param user_data
* @return 0 0
*/
typedef int (*cocoon_middleware_func_t)(http_request_t *req, cocoon_socket_t fd, void *user_data);
/* === 注册表 API === */
/**
* cocoon_middleware_register -
*
* 16
*
* @param name
* @param func
* @param user_data NULL
* @return 0 -1
*/
int cocoon_middleware_register(const char *name, cocoon_middleware_func_t func, void *user_data);
/**
* cocoon_middleware_unregister -
*
* @param name
* @return 0 -1
*/
int cocoon_middleware_unregister(const char *name);
/**
* cocoon_middleware_run -
*
* 0
*
* @param req HTTP
* @param fd socket
* @return 0 0
*/
int cocoon_middleware_run(http_request_t *req, cocoon_socket_t fd);
/**
* cocoon_middleware_cleanup -
*/
void cocoon_middleware_cleanup(void);
/* === 内置中间件 === */
/**
* cocoon_middleware_cors - CORS
*
* OPTIONS 204 No Content
* 0
*
* cocoon.json "cors_enabled": true 使
* CORS static.c / http.c
*
* @param req HTTP
* @param fd socket
* @param user_data 使 NULL
* @return 0 1 OPTIONS
*/
int cocoon_middleware_cors(http_request_t *req, cocoon_socket_t fd, void *user_data);
/**
* cocoon_middleware_basic_auth - Basic HTTP
*
* Authorization: Basic 401
* cocoon_middleware_config_t user_data
*
* @param req HTTP
* @param fd socket
* @param user_data cocoon_middleware_config_t
* @return 0 1 401
*/
int cocoon_middleware_basic_auth(http_request_t *req, cocoon_socket_t fd, void *user_data);
/**
* cocoon_middleware_rate_limit - IP
*
* 使 IP
* 429 Too Many Requests
*
* @param req HTTP
* @param fd socket
* @param user_data cocoon_middleware_config_t rate_limit
* @return 0 1 429
*/
int cocoon_middleware_rate_limit(http_request_t *req, cocoon_socket_t fd, void *user_data);
/**
* cocoon_middleware_init_builtin -
*
*
*
* @param config
*/
void cocoon_middleware_init_builtin(const cocoon_middleware_config_t *config);
#endif /* COCOON_MIDDLEWARE_H */

View File

@ -26,6 +26,7 @@
#include "access_log.h"
#include "websocket.h"
#include "platform.h"
#include "middleware.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -63,6 +64,7 @@ typedef struct {
struct server_context {
cocoon_socket_t listen_fd; /**< 监听 socket */
cocoon_config_t config; /**< 配置副本 */
cocoon_middleware_config_t mw_config; /**< 中间件配置(持久化,避免栈变量悬空) */
volatile int running; /**< 运行标志 */
coco_sched_t *sched; /**< 协程调度器 */
};
@ -363,6 +365,15 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
}
conn->buf_len -= (size_t)parsed;
/* 执行中间件链 */
if (cocoon_middleware_run(&req, conn->fd) != 0) {
/* 某个中间件已短路请求,清理并返回 */
access_log_write((struct sockaddr *)&conn->client_addr, conn->addr_len,
&req, conn->response_status, -1);
http_request_free(&req);
return req.keep_alive;
}
/* 读取请求体(如果需要) */
if (req.content_length > 0) {
size_t need = (size_t)req.content_length;
@ -1030,6 +1041,13 @@ server_context_t *server_create(const cocoon_config_t *config) {
}
}
/* 初始化内置中间件(使用 ctx->mw_config 避免栈变量悬空) */
ctx->mw_config.cors_enabled = ctx->config.cors_enabled;
ctx->mw_config.auth_user = ctx->config.auth_user;
ctx->mw_config.auth_pass = ctx->config.auth_pass;
ctx->mw_config.rate_limit = ctx->config.rate_limit;
cocoon_middleware_init_builtin(&ctx->mw_config);
return ctx;
}
@ -1103,6 +1121,8 @@ void server_stop(server_context_t *ctx) {
void server_destroy(server_context_t *ctx) {
if (!ctx) return;
cocoon_middleware_cleanup();
tls_destroy_context();
if (ctx->listen_fd != COCOON_INVALID_SOCKET) {

View File

@ -21,6 +21,13 @@
#include <zlib.h>
#include <brotli/encode.h>
static bool g_cors_enabled = false;
void static_set_cors(bool enabled) {
g_cors_enabled = enabled;
log_info("CORS 支持已 %s", enabled ? "启用" : "禁用");
}
/**
* is_compressible_mime - MIME
*

View File

@ -62,4 +62,13 @@ int static_serve_directory(int fd, const http_request_t *req,
*/
int static_send_error(int fd, int status_code, bool keep_alive);
/**
* static_set_cors - CORS
*
* HTTP Access-Control-Allow-Origin: *
*
* @param enabled true false
*/
void static_set_cors(bool enabled);
#endif /* COCOON_STATIC_H */

View File

@ -18,9 +18,23 @@ TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"; kill_server' EXIT
kill_server() {
pkill -f "cocoon.*-p 9999" 2>/dev/null || true
pkill -f "cocoon.*--cert" 2>/dev/null || true
local pids
pids=$(pgrep -f "cocoon.*-p 9999" 2>/dev/null || true)
if [[ -n "$pids" ]]; then
echo "$pids" | xargs kill -9 2>/dev/null || true
fi
pids=$(pgrep -f "cocoon.*--cert" 2>/dev/null || true)
if [[ -n "$pids" ]]; then
echo "$pids" | xargs kill -9 2>/dev/null || true
fi
sleep 0.5
# 确保端口已释放
for i in {1..20}; do
if ! ss -tlnp 2>/dev/null | grep -q ':9999'; then
break
fi
sleep 0.1
done
}
# 启动服务器
@ -29,7 +43,7 @@ start_server() {
$SERVER -r "$ROOT" -p 9999 -l debug > "$TMPDIR/server.log" 2>&1 &
local pid=$!
for i in {1..30}; do
if curl -s -o /dev/null "$BASE/" 2>/dev/null; then
if nc -z localhost 9999 2>/dev/null; then
echo "[test] 服务器就绪 (PID $pid)"
return 0
fi
@ -721,6 +735,88 @@ else
fi
# WebSocket 测试
# 停止 HTTP 服务器,恢复默认服务器
kill_server
sleep 1
start_server
# 中间件测试
echo ""
echo "=== 中间件测试 ==="
# CORS 测试
kill_server
sleep 1
$SERVER -r "$ROOT" -p 9999 --cors > "$TMPDIR/server_cors.log" 2>&1 &
for i in {1..30}; do
if nc -z localhost 9999 2>/dev/null; then break; fi
sleep 0.1
done
cors_status=$(curl -s -o /dev/null -w "%{http_code}" -X OPTIONS "$BASE/" -H "Origin: http://example.com" -H "Access-Control-Request-Method: POST")
if [[ "$cors_status" == "204" ]]; then
echo " ✓ CORS OPTIONS 预检 — HTTP 204"
pass
else
echo " ✗ CORS OPTIONS 预检 — 期望 204, 实际 $cors_status"
fail
fi
# Basic Auth 测试
kill_server
sleep 1
$SERVER -r "$ROOT" -p 9999 --auth-user "admin" --auth-pass "secret" > "$TMPDIR/server_auth.log" 2>&1 &
for i in {1..30}; do
if nc -z localhost 9999 2>/dev/null; then break; fi
sleep 0.1
done
auth_no_cred=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/")
if [[ "$auth_no_cred" == "401" ]]; then
echo " ✓ Basic Auth 无认证 — HTTP 401"
pass
else
echo " ✗ Basic Auth 无认证 — 期望 401, 实际 $auth_no_cred"
fail
fi
auth_with_cred=$(curl -s -o /dev/null -w "%{http_code}" -u "admin:secret" "$BASE/")
if [[ "$auth_with_cred" == "200" ]]; then
echo " ✓ Basic Auth 正确认证 — HTTP 200"
pass
else
echo " ✗ Basic Auth 正确认证 — 期望 200, 实际 $auth_with_cred"
fail
fi
auth_wrong_pass=$(curl -s -o /dev/null -w "%{http_code}" -u "admin:wrong" "$BASE/")
if [[ "$auth_wrong_pass" == "401" ]]; then
echo " ✓ Basic Auth 错误密码 — HTTP 401"
pass
else
echo " ✗ Basic Auth 错误密码 — 期望 401, 实际 $auth_wrong_pass"
fail
fi
# Rate Limit 测试
kill_server
sleep 1
$SERVER -r "$ROOT" -p 9999 --rate-limit 1 > "$TMPDIR/server_ratelimit.log" 2>&1 &
for i in {1..30}; do
if nc -z localhost 9999 2>/dev/null; then break; fi
sleep 0.1
done
rl_first=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/")
rl_second=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/")
if [[ "$rl_first" == "200" && "$rl_second" == "429" ]]; then
echo " ✓ Rate Limit 限流 — 第1次 200, 第2次 429"
pass
else
echo " ✗ Rate Limit 限流 — 期望 200+429, 实际 $rl_first+$rl_second"
fail
fi
# 恢复默认服务器
kill_server
sleep 1
start_server
echo ""
echo "=== WebSocket 测试 ==="

View File

@ -126,7 +126,8 @@ void test_merge_override_all(void) {
.brotli_enabled = false
};
config_merge(&base, &cmdline,
true, true, true, true, true, true, true, true, true, true, true, true);
true, true, true, true, true, true, true, true, true, true, true, true,
false, false, false, false);
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir);
TEST_ASSERT_EQUAL(9090, base.port);
TEST_ASSERT_TRUE(base.threaded);
@ -155,7 +156,8 @@ void test_merge_no_override(void) {
.log_level = LOG_LEVEL_DEBUG
};
config_merge(&base, &cmdline,
false, false, false, false, false, false, false, false, false, false, false, false);
false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false);
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir);
TEST_ASSERT_EQUAL(8080, base.port);
TEST_ASSERT_FALSE(base.threaded);
@ -178,7 +180,8 @@ void test_merge_partial_override(void) {
.log_level = LOG_LEVEL_DEBUG
};
config_merge(&base, &cmdline,
true, false, false, true, false, false, false, false, false, false, false, false);
true, false, false, true, false, false, false, false, false, false, false, false,
false, false, false, false);
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir); /* overridden */
TEST_ASSERT_EQUAL(8080, base.port); /* not overridden */
TEST_ASSERT_EQUAL(2, base.num_workers); /* not overridden */
@ -243,7 +246,8 @@ void test_merge_cmdline_null_root_dir(void) {
/* cmdline root_dir 为 NULL不应覆盖 base */
cocoon_config_t base = {.root_dir = strdup("/old"), .port = 8080};
cocoon_config_t cmdline = {.root_dir = NULL, .port = 9090};
config_merge(&base, &cmdline, true, true, false, false, false, false, false, false, false, false, false, false);
config_merge(&base, &cmdline, true, true, false, false, false, false, false, false, false, false, false, false,
false, false, false, false);
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir); /* NULL 不覆盖 */
TEST_ASSERT_EQUAL(9090, base.port); /* port 覆盖 */
free((void *)base.root_dir);
@ -251,7 +255,8 @@ void test_merge_cmdline_null_root_dir(void) {
void test_merge_null_safety(void) {
/* 不应 crash */
config_merge(NULL, NULL, true, true, true, true, true, true, true, true, true, true, true, true);
config_merge(NULL, NULL, true, true, true, true, true, true, true, true, true, true, true, true,
true, true, true, true);
TEST_ASSERT_TRUE(1);
}