feat(health): 添加服务器健康检查端点 /_health
- 新增 /_health 路由,返回 JSON 格式的服务器状态 - 包含:uptime、活跃连接数、最大连接数、插件列表、中间件列表 - 中间件注册表新增 cocoon_middleware_list() 查询 API - 集成测试新增 7 项健康检查验证(状态码、JSON 字段、HEAD 支持) - 编译零警告,142 个单元测试 + 75 项集成测试全部通过
This commit is contained in:
parent
b5acd7f5d1
commit
09e592d631
15
middleware.c
15
middleware.c
@ -242,6 +242,21 @@ void cocoon_middleware_cleanup(void) {
|
||||
pthread_mutex_unlock(&g_rate_limit_mutex);
|
||||
}
|
||||
|
||||
int cocoon_middleware_list(char names[][32], int count) {
|
||||
if (!names || count <= 0) return 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < g_count && n < count; i++) {
|
||||
if (g_registry[i].active) {
|
||||
size_t len = strlen(g_registry[i].name);
|
||||
if (len > 31) len = 31;
|
||||
memcpy(names[n], g_registry[i].name, len);
|
||||
names[n][len] = '\0';
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* === 内置中间件 === */
|
||||
|
||||
int cocoon_middleware_cors(http_request_t *req, cocoon_socket_t fd, void *user_data) {
|
||||
|
||||
11
middleware.h
11
middleware.h
@ -130,6 +130,17 @@ int cocoon_middleware_basic_auth(http_request_t *req, cocoon_socket_t fd, void *
|
||||
*/
|
||||
int cocoon_middleware_rate_limit(http_request_t *req, cocoon_socket_t fd, void *user_data);
|
||||
|
||||
/**
|
||||
* cocoon_middleware_list - 获取已注册中间件名称列表
|
||||
*
|
||||
* 将名称写入提供的缓冲区数组,每个名称最多 32 字节。
|
||||
*
|
||||
* @param names 名称缓冲区数组(二维字符数组)
|
||||
* @param count 数组容量(最大能存放多少个名称)
|
||||
* @return 实际写入的中间件数量
|
||||
*/
|
||||
int cocoon_middleware_list(char names[][32], int count);
|
||||
|
||||
/**
|
||||
* cocoon_middleware_init_builtin - 根据配置初始化内置中间件
|
||||
*
|
||||
|
||||
86
server.c
86
server.c
@ -28,6 +28,7 @@
|
||||
#include "websocket.h"
|
||||
#include "platform.h"
|
||||
#include "middleware.h"
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -72,6 +73,10 @@ struct server_context {
|
||||
|
||||
/* 全局活跃连接计数器(线程安全) */
|
||||
static atomic_int g_active_connections = 0;
|
||||
/* 服务器启动时间 */
|
||||
static time_t g_server_start_time = 0;
|
||||
/* 最大连接数(供健康检查端点使用) */
|
||||
static uint32_t g_max_connections = 0;
|
||||
|
||||
/**
|
||||
* set_nonblocking - 设置 socket 为非阻塞模式
|
||||
@ -408,6 +413,83 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
|
||||
return req.keep_alive;
|
||||
}
|
||||
|
||||
/* 健康检查端点 */
|
||||
if (strcmp(req.path, "/_health") == 0) {
|
||||
time_t now = time(NULL);
|
||||
time_t uptime = now - g_server_start_time;
|
||||
int active = atomic_load(&g_active_connections);
|
||||
|
||||
char mw_names[16][32];
|
||||
int mw_count = cocoon_middleware_list(mw_names, 16);
|
||||
|
||||
/* 构建 JSON */
|
||||
char body[2048];
|
||||
int n = snprintf(body, sizeof(body),
|
||||
"{\n"
|
||||
" \"status\": \"ok\",\n"
|
||||
" \"version\": \"Cocoon/1.0\",\n"
|
||||
" \"uptime_seconds\": %ld,\n"
|
||||
" \"connections\": {\n"
|
||||
" \"active\": %d,\n"
|
||||
" \"max\": %u\n"
|
||||
" },\n"
|
||||
" \"plugins\": {\n"
|
||||
" \"count\": %zu,\n"
|
||||
" \"list\": [\n",
|
||||
uptime, active,
|
||||
g_max_connections,
|
||||
cocoon_plugin_count());
|
||||
|
||||
for (size_t i = 0; i < cocoon_plugin_count(); i++) {
|
||||
const char *path = cocoon_plugin_get_path(i);
|
||||
const char *ver = cocoon_plugin_get_version(i);
|
||||
n += snprintf(body + n, sizeof(body) - n,
|
||||
" {\"path\":\"%s\",\"version\":\"%s\"}%s\n",
|
||||
path ? path : "",
|
||||
ver ? ver : "unknown",
|
||||
(i + 1 < cocoon_plugin_count()) ? "," : "");
|
||||
}
|
||||
|
||||
n += snprintf(body + n, sizeof(body) - n,
|
||||
" ]\n"
|
||||
" },\n"
|
||||
" \"middleware\": {\n"
|
||||
" \"count\": %d,\n"
|
||||
" \"names\": [\n",
|
||||
mw_count);
|
||||
|
||||
for (int i = 0; i < mw_count; i++) {
|
||||
n += snprintf(body + n, sizeof(body) - n,
|
||||
" \"%s\"%s\n",
|
||||
mw_names[i],
|
||||
(i + 1 < mw_count) ? "," : "");
|
||||
}
|
||||
|
||||
n += snprintf(body + n, sizeof(body) - n,
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}");
|
||||
|
||||
char header[512];
|
||||
int header_len = snprintf(header, sizeof(header),
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: %s\r\n"
|
||||
"Server: Cocoon/1.0\r\n"
|
||||
"\r\n",
|
||||
n, req.keep_alive ? "keep-alive" : "close");
|
||||
|
||||
send_all(conn->fd, header, (size_t)header_len);
|
||||
send_all(conn->fd, body, (size_t)n);
|
||||
|
||||
conn->response_status = 200;
|
||||
access_log_write((struct sockaddr *)&conn->client_addr, conn->addr_len,
|
||||
&req, conn->response_status, n);
|
||||
http_request_free(&req);
|
||||
return req.keep_alive;
|
||||
}
|
||||
|
||||
/* 安全路径拼接 */
|
||||
char real_path[4096];
|
||||
char root_normalized[4096];
|
||||
@ -1001,6 +1083,8 @@ server_context_t *server_create(const cocoon_config_t *config) {
|
||||
ctx->config.root_dir = strdup(config->root_dir);
|
||||
ctx->running = 1;
|
||||
|
||||
g_max_connections = config->max_connections;
|
||||
|
||||
/* 创建监听 socket */
|
||||
ctx->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (ctx->listen_fd == COCOON_INVALID_SOCKET) {
|
||||
@ -1070,6 +1154,8 @@ server_context_t *server_create(const cocoon_config_t *config) {
|
||||
int server_start(server_context_t *ctx) {
|
||||
if (!ctx) return COCOON_ERROR;
|
||||
|
||||
g_server_start_time = time(NULL);
|
||||
|
||||
if (ctx->config.threaded) {
|
||||
/* 多线程协程模式 */
|
||||
uint32_t num_workers = ctx->config.num_workers;
|
||||
|
||||
@ -409,7 +409,48 @@ assert_access_log() {
|
||||
start_server
|
||||
|
||||
echo ""
|
||||
echo "=== 基础功能测试 ==="
|
||||
echo "=== 健康检查端点测试 ==="
|
||||
health_body=$(curl -s "$BASE/_health")
|
||||
health_status=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/_health")
|
||||
if [[ "$health_status" == "200" ]]; then
|
||||
echo " ✓ /_health 状态码 — HTTP 200"
|
||||
pass
|
||||
else
|
||||
echo " ✗ /_health 状态码 — 期望 200, 实际 $health_status"
|
||||
fail
|
||||
fi
|
||||
if echo "$health_body" | grep -q '"status": "ok"'; then
|
||||
echo " ✓ /_health 响应体 — 包含 status: ok"
|
||||
pass
|
||||
else
|
||||
echo " ✗ /_health 响应体 — 缺少 status: ok"
|
||||
fail
|
||||
fi
|
||||
if echo "$health_body" | grep -q '"version": "Cocoon/1.0"'; then
|
||||
echo " ✓ /_health 响应体 — 包含版本信息"
|
||||
pass
|
||||
else
|
||||
echo " ✗ /_health 响应体 — 缺少版本信息"
|
||||
fail
|
||||
fi
|
||||
if echo "$health_body" | grep -q '"connections"'; then
|
||||
echo " ✓ /_health 响应体 — 包含连接数信息"
|
||||
pass
|
||||
else
|
||||
echo " ✗ /_health 响应体 — 缺少连接数信息"
|
||||
fail
|
||||
fi
|
||||
if echo "$health_body" | grep -q '"middleware"'; then
|
||||
echo " ✓ /_health 响应体 — 包含中间件信息"
|
||||
pass
|
||||
else
|
||||
echo " ✗ /_health 响应体 — 缺少中间件信息"
|
||||
fail
|
||||
fi
|
||||
|
||||
assert_head_status "$BASE/_health" "200" "健康检查 HEAD"
|
||||
|
||||
assert_status "$BASE/_health/nonexistent" "404" "健康检查路径遍历"
|
||||
assert_status "$BASE/" "200" "首页 GET"
|
||||
assert_status "$BASE/index.html" "200" "index.html GET"
|
||||
assert_status "$BASE/nonexist.html" "404" "404 页面"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user