/** * dashboard.c - 内置管理 Dashboard 实现 * * 提供 `/_status` HTML 管理页面和 `/_status/events` SSE 实时指标流。 * HTML 完全自包含(内联 CSS/JS),不依赖外部文件。 * * @author xfy */ #include "dashboard.h" #include "sse.h" #include "log.h" #include "platform.h" #include "../coco/include/coco.h" #include #include #include #include /* 内联的 Dashboard HTML 页面 */ static const char DASHBOARD_HTML[] = "\n" "\n" "\n" "\n" "\n" "Cocoon Dashboard\n" "\n" "\n" "\n" "
\n" "

🪵 Cocoon Dashboard

\n" "
Connecting...
\n" "
\n" "

Uptime

--
\n" "

Active Connections

--
\n" "

Total Requests

--
\n" "

Response Distribution

--
\n" "
" "
\n" "
\n" "
\n" "
\n" "\n" "\n" ""; /** * dashboard_send_html - 发送 Dashboard HTML 页面 */ static int dashboard_send_html(int fd, bool keep_alive) { size_t html_len = strlen(DASHBOARD_HTML); char header[512]; int header_len = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=utf-8\r\n" "Content-Length: %zu\r\n" "Connection: %s\r\n" "Server: Cocoon/1.0\r\n" "\r\n", html_len, keep_alive ? "keep-alive" : "close"); if (send(fd, header, (size_t)header_len, 0) < 0) return -1; if (send(fd, DASHBOARD_HTML, html_len, 0) < 0) return -1; return 0; } /** * dashboard_build_metrics_json - 构建当前指标 JSON */ static int dashboard_build_metrics_json(char *buf, size_t buflen) { time_t now = time(NULL); time_t uptime = (g_server_start_time > 0) ? (now - g_server_start_time) : 0; int active = atomic_load(&g_active_connections); unsigned int total = atomic_load(&g_total_requests); unsigned int r2xx = atomic_load(&g_response_2xx); unsigned int r3xx = atomic_load(&g_response_3xx); unsigned int r4xx = atomic_load(&g_response_4xx); unsigned int r5xx = atomic_load(&g_response_5xx); return snprintf(buf, buflen, "{" "\"uptime\":%ld," "\"active\":%d," "\"max\":%u," "\"total\":%u," "\"r2xx\":%u," "\"r3xx\":%u," "\"r4xx\":%u," "\"r5xx\":%u," "\"version\":\"Cocoon/1.0\"" "}", uptime, active, g_max_connections, total, r2xx, r3xx, r4xx, r5xx); } /** * dashboard_handle_request - 处理 /_status 页面请求 */ bool dashboard_handle_request(int fd, const http_request_t *req) { if (!req || strcmp(req->path, "/_status") != 0) { return false; } if (req->method != HTTP_GET) { const char *resp = "HTTP/1.1 405 Method Not Allowed\r\n" "Allow: GET\r\n" "Content-Length: 0\r\n" "Connection: close\r\n" "Server: Cocoon/1.0\r\n" "\r\n"; send(fd, resp, strlen(resp), 0); return true; } log_info("Dashboard 页面请求 fd=%d", fd); dashboard_send_html(fd, req->keep_alive); return true; } /** * dashboard_sse_handle_request - 处理 /_status/events SSE 流 * * 每 2 秒推送一次 JSON 指标,包含心跳注释防止代理超时。 */ bool dashboard_sse_handle_request(int fd, const http_request_t *req) { if (!req || strcmp(req->path, "/_status/events") != 0) { return false; } if (req->method != HTTP_GET) { const char *resp = "HTTP/1.1 405 Method Not Allowed\r\n" "Allow: GET\r\n" "Content-Length: 0\r\n" "Connection: close\r\n" "Server: Cocoon/1.0\r\n" "\r\n"; send(fd, resp, strlen(resp), 0); return true; } log_info("Dashboard SSE 连接建立 fd=%d", fd); if (sse_send_headers(fd) != 0) { log_warn("Dashboard SSE 发送响应头失败 fd=%d", fd); return true; } /* 发送连接确认事件 */ sse_send_event(fd, "connected", "{\"status\":\"ok\"}", 0); uint32_t tick = 0; while (1) { char json[1024]; int json_len = dashboard_build_metrics_json(json, sizeof(json)); if (json_len > 0) { if (sse_send_event(fd, "metrics", json, tick + 1) != 0) { log_info("Dashboard SSE 客户端断开 fd=%d", fd); break; } } tick++; /* 每 3 个事件(6 秒)发送一次心跳注释 */ if (tick % 3 == 0) { if (sse_send_comment(fd, "heartbeat") != 0) { break; } } /* 2 秒间隔(协程安全) */ coco_sleep(2000); } log_info("Dashboard SSE 连接结束 fd=%d", fd); return true; }