FastCGI 服务器集成完成:协议核心、配置解析、请求分发、连接池管理
实现内容: 1. 新增 fcgi_handler.h / fcgi_handler.c 模块: - FastCGI 协议核心(BEGIN_REQUEST / PARAMS / STDIN / FCGI_END_REQUEST) - CGI 参数构建(REQUEST_METHOD, SCRIPT_NAME, PATH_INFO, QUERY_STRING 等) - HTTP 响应解析(Status 头提取、默认 200 回退) - 连接池(TCP / Unix Socket 复用)与路由匹配 2. 配置解析扩展(config.c): - 支持 --fastcgi-prefix / --fastcgi-target 等命令行参数 - 支持配置文件的 fastcgi 规则段 - 最大 4 条规则,可配置 pool_size、timeout_ms、unix_socket 3. 请求处理链集成(server.c): - handle_request 中反向代理之后、静态资源之前插入 FastCGI 分发 - server_context 添加 fcgi_config 字段 - server_create / server_destroy 中初始化/销毁连接池 4. Makefile 更新: - 加入 fcgi_handler.c 编译目标 - 测试规则同步更新 5. .gitignore 修复:拆分 tests/fixtures/uploads/ 为独立规则 测试:458 单元测试 + 115 集成测试全部通过 关联:Phase 5 应用网关 — FastCGI 服务器集成
This commit is contained in:
parent
ffa03c2163
commit
a09452b43c
6
.gitignore
vendored
6
.gitignore
vendored
@ -8,6 +8,10 @@ tests/unit/test_*
|
|||||||
!tests/unit/test_*.c
|
!tests/unit/test_*.c
|
||||||
|
|
||||||
# 测试编译产物(无后缀名的二进制文件)
|
# 测试编译产物(无后缀名的二进制文件)
|
||||||
tests/unit/test_*.otests/fixtures/uploads/
|
tests/unit/test_*
|
||||||
|
!tests/unit/test_*.c
|
||||||
|
|
||||||
|
# 上传目录
|
||||||
|
tests/fixtures/uploads/
|
||||||
plugins/*.so
|
plugins/*.so
|
||||||
www/uploads/
|
www/uploads/
|
||||||
|
|||||||
6
Makefile
6
Makefile
@ -20,7 +20,7 @@ PREFIX ?= /usr/local
|
|||||||
BINDIR = $(PREFIX)/bin
|
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 middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c middleware_ext.c load_balance.c grpc.c http3.c sse.c fastcgi.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 plugin.c proxy.c proxy_tls.c healthcheck.c middleware_ext.c load_balance.c grpc.c http3.c sse.c fastcgi.c fcgi_handler.c
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
TARGET = cocoon
|
TARGET = cocoon
|
||||||
|
|
||||||
@ -90,8 +90,8 @@ unit-test: $(UNIT_TEST_BINS)
|
|||||||
fi
|
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 middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c $(UNITY_SRC)
|
$(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 plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.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 plugin.c proxy.c proxy_tls.c healthcheck.c sse.c $(UNITY_SRC) $(LDFLAGS)
|
$(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 plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c $(UNITY_SRC) $(LDFLAGS)
|
||||||
|
|
||||||
$(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC)
|
$(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
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) -lm
|
||||||
|
|||||||
11
cocoon.h
11
cocoon.h
@ -86,6 +86,17 @@ typedef struct cocoon_config {
|
|||||||
/* 虚拟主机 */
|
/* 虚拟主机 */
|
||||||
cocoon_vhost_t vhosts[COCOON_MAX_VHOSTS];
|
cocoon_vhost_t vhosts[COCOON_MAX_VHOSTS];
|
||||||
size_t num_vhosts;
|
size_t num_vhosts;
|
||||||
|
/* FastCGI 配置 */
|
||||||
|
#define COCOON_MAX_FASTCGI_RULES 4
|
||||||
|
struct {
|
||||||
|
char prefix[256]; /**< 路径前缀匹配 */
|
||||||
|
char host[256]; /**< 后端主机或 Unix socket 路径 */
|
||||||
|
int port; /**< 端口(TCP 时有效) */
|
||||||
|
bool is_unix_socket; /**< 是否为 Unix domain socket */
|
||||||
|
int pool_size; /**< 连接池大小 */
|
||||||
|
int timeout_ms; /**< 请求超时 */
|
||||||
|
} fastcgi[COCOON_MAX_FASTCGI_RULES];
|
||||||
|
size_t num_fastcgi;
|
||||||
} cocoon_config_t;
|
} cocoon_config_t;
|
||||||
|
|
||||||
/* === 服务器生命周期 API === */
|
/* === 服务器生命周期 API === */
|
||||||
|
|||||||
69
config.c
69
config.c
@ -566,6 +566,75 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
|
|||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "[Config] 第 %d 行: vhosts 期望数组\n", val.line);
|
fprintf(stderr, "[Config] 第 %d 行: vhosts 期望数组\n", val.line);
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(key_str, "fastcgi") == 0) {
|
||||||
|
if (val.type == TOKEN_LBRACKET) {
|
||||||
|
while (1) {
|
||||||
|
token_t item = parser_next_token(&p);
|
||||||
|
if (item.type == TOKEN_RBRACKET) break;
|
||||||
|
if (item.type == TOKEN_LBRACE) {
|
||||||
|
char prefix[256] = {0};
|
||||||
|
char host[256] = {0};
|
||||||
|
int port = 0;
|
||||||
|
bool is_unix = false;
|
||||||
|
int pool_size = 4;
|
||||||
|
int timeout_ms = 30000;
|
||||||
|
while (1) {
|
||||||
|
token_t fkey = parser_next_token(&p);
|
||||||
|
if (fkey.type == TOKEN_RBRACE) break;
|
||||||
|
if (fkey.type != TOKEN_STRING) {
|
||||||
|
token_t skip_sep = parser_next_token(&p);
|
||||||
|
if (skip_sep.type == TOKEN_RBRACE) break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!token_expect(&p, TOKEN_COLON)) break;
|
||||||
|
token_t fval = parser_next_token(&p);
|
||||||
|
char *fk = token_str_dup(&fkey);
|
||||||
|
if (strcmp(fk, "prefix") == 0 && fval.type == TOKEN_STRING) {
|
||||||
|
char *v = token_str_dup(&fval);
|
||||||
|
if (v) { strncpy(prefix, v, sizeof(prefix)-1); free(v); }
|
||||||
|
} else if (strcmp(fk, "host") == 0 && fval.type == TOKEN_STRING) {
|
||||||
|
char *v = token_str_dup(&fval);
|
||||||
|
if (v) { strncpy(host, v, sizeof(host)-1); free(v); }
|
||||||
|
} else if (strcmp(fk, "port") == 0 && fval.type == TOKEN_NUMBER) {
|
||||||
|
port = (int)token_to_long(&fval);
|
||||||
|
} else if (strcmp(fk, "unix_socket") == 0) {
|
||||||
|
if (fval.type == TOKEN_TRUE) is_unix = true;
|
||||||
|
else if (fval.type == TOKEN_FALSE) is_unix = false;
|
||||||
|
} else if (strcmp(fk, "pool_size") == 0 && fval.type == TOKEN_NUMBER) {
|
||||||
|
pool_size = (int)token_to_long(&fval);
|
||||||
|
} else if (strcmp(fk, "timeout_ms") == 0 && fval.type == TOKEN_NUMBER) {
|
||||||
|
timeout_ms = (int)token_to_long(&fval);
|
||||||
|
}
|
||||||
|
free(fk);
|
||||||
|
token_t fsep = parser_next_token(&p);
|
||||||
|
if (fsep.type == TOKEN_RBRACE) break;
|
||||||
|
if (fsep.type != TOKEN_COMMA) break;
|
||||||
|
}
|
||||||
|
if (prefix[0] && host[0] && config->num_fastcgi < COCOON_MAX_FASTCGI_RULES) {
|
||||||
|
size_t i = config->num_fastcgi;
|
||||||
|
strncpy(config->fastcgi[i].prefix, prefix, sizeof(config->fastcgi[0].prefix)-1);
|
||||||
|
config->fastcgi[i].prefix[sizeof(config->fastcgi[0].prefix)-1] = '\0';
|
||||||
|
strncpy(config->fastcgi[i].host, host, sizeof(config->fastcgi[0].host)-1);
|
||||||
|
config->fastcgi[i].host[sizeof(config->fastcgi[0].host)-1] = '\0';
|
||||||
|
config->fastcgi[i].port = port;
|
||||||
|
config->fastcgi[i].is_unix_socket = is_unix;
|
||||||
|
config->fastcgi[i].pool_size = pool_size > 0 ? pool_size : 4;
|
||||||
|
config->fastcgi[i].timeout_ms = timeout_ms > 0 ? timeout_ms : 30000;
|
||||||
|
config->num_fastcgi++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "[Config] 第 %d 行: fastcgi 数组期望对象项\n", item.line);
|
||||||
|
}
|
||||||
|
token_t sep = parser_next_token(&p);
|
||||||
|
if (sep.type == TOKEN_RBRACKET) break;
|
||||||
|
if (sep.type != TOKEN_COMMA) {
|
||||||
|
fprintf(stderr, "[Config] 第 %d 行: fastcgi 数组期望 ',' 或 ']'\n", sep.line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "[Config] 第 %d 行: fastcgi 期望数组\n", val.line);
|
||||||
|
}
|
||||||
} /* 其他字段:忽略(未来扩展预留) */
|
} /* 其他字段:忽略(未来扩展预留) */
|
||||||
|
|
||||||
free(key_str);
|
free(key_str);
|
||||||
|
|||||||
280
fcgi_handler.c
Normal file
280
fcgi_handler.c
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
/**
|
||||||
|
* fcgi_handler.c - FastCGI 服务器集成实现
|
||||||
|
*
|
||||||
|
* 将 HTTP 请求转换为 FastCGI 请求,通过连接池转发到后端。
|
||||||
|
*
|
||||||
|
* @author xfy
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fcgi_handler.h"
|
||||||
|
#include "static.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build_cgi_params - 从 HTTP 请求构建 CGI 环境变量
|
||||||
|
*
|
||||||
|
* 构建标准 CGI 参数(SCRIPT_NAME, PATH_INFO, REQUEST_METHOD 等)。
|
||||||
|
*/
|
||||||
|
static bool build_cgi_params(fcgi_request_t *fcgi_req, const http_request_t *req,
|
||||||
|
const char *script_name, const char *path_info) {
|
||||||
|
char buf[256];
|
||||||
|
|
||||||
|
/* 从 path 中分离 query string */
|
||||||
|
const char *query = "";
|
||||||
|
char path_copy[HTTP_MAX_PATH];
|
||||||
|
strncpy(path_copy, req->path, sizeof(path_copy) - 1);
|
||||||
|
path_copy[sizeof(path_copy) - 1] = '\0';
|
||||||
|
char *q = strchr(path_copy, '?');
|
||||||
|
if (q) {
|
||||||
|
*q = '\0';
|
||||||
|
query = q + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 基本 CGI 变量 */
|
||||||
|
if (!fcgi_add_param(fcgi_req, "REQUEST_METHOD", http_method_str(req->method))) return false;
|
||||||
|
if (!fcgi_add_param(fcgi_req, "SCRIPT_NAME", script_name)) return false;
|
||||||
|
if (!fcgi_add_param(fcgi_req, "PATH_INFO", path_info ? path_info : "")) return false;
|
||||||
|
if (!fcgi_add_param(fcgi_req, "QUERY_STRING", query)) return false;
|
||||||
|
|
||||||
|
/* 协议信息 */
|
||||||
|
if (!fcgi_add_param(fcgi_req, "SERVER_PROTOCOL", req->version[0] ? req->version : "HTTP/1.1")) return false;
|
||||||
|
|
||||||
|
/* 内容长度 */
|
||||||
|
if (req->content_length > 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "%ld", (long)req->content_length);
|
||||||
|
if (!fcgi_add_param(fcgi_req, "CONTENT_LENGTH", buf)) return false;
|
||||||
|
}
|
||||||
|
if (req->content_type[0]) {
|
||||||
|
if (!fcgi_add_param(fcgi_req, "CONTENT_TYPE", req->content_type)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 客户端信息 */
|
||||||
|
if (!fcgi_add_param(fcgi_req, "REMOTE_ADDR", "")) return false;
|
||||||
|
|
||||||
|
/* HTTP 头转 CGI 变量 */
|
||||||
|
for (int i = 0; i < req->num_headers; i++) {
|
||||||
|
const char *name = req->headers[i].name;
|
||||||
|
const char *value = req->headers[i].value;
|
||||||
|
if (!name || !value) continue;
|
||||||
|
|
||||||
|
/* 跳过 Host 头(已处理) */
|
||||||
|
if (strcasecmp(name, "Host") == 0) {
|
||||||
|
if (!fcgi_add_param(fcgi_req, "HTTP_HOST", value)) return false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* 其他头转 HTTP_ 前缀 */
|
||||||
|
char cgi_name[256];
|
||||||
|
int n = snprintf(cgi_name, sizeof(cgi_name), "HTTP_%s", name);
|
||||||
|
if (n < 0 || (size_t)n >= sizeof(cgi_name)) continue;
|
||||||
|
|
||||||
|
/* 将 - 替换为 _ */
|
||||||
|
for (int j = 0; cgi_name[j]; j++) {
|
||||||
|
if (cgi_name[j] == '-') cgi_name[j] = '_';
|
||||||
|
if (cgi_name[j] >= 'a' && cgi_name[j] <= 'z') cgi_name[j] -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fcgi_add_param(fcgi_req, cgi_name, value)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* send_http_response - 将 FastCGI 响应回写给客户端
|
||||||
|
*
|
||||||
|
* 解析 FastCGI 响应,提取 HTTP 状态码和响应体,
|
||||||
|
* 构造标准 HTTP 响应发给客户端。
|
||||||
|
*/
|
||||||
|
static bool send_http_response(cocoon_socket_t client_fd, fcgi_response_t *resp,
|
||||||
|
bool keep_alive) {
|
||||||
|
if (!resp || !resp->complete || !resp->stdout_data) {
|
||||||
|
static_send_error(client_fd, 502, keep_alive);
|
||||||
|
return keep_alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提取 HTTP 状态码 */
|
||||||
|
int status = fcgi_extract_status(resp->stdout_data, resp->stdout_len);
|
||||||
|
if (status <= 0) status = 200;
|
||||||
|
|
||||||
|
/* 提取响应体 */
|
||||||
|
const char *body = NULL;
|
||||||
|
size_t body_len = 0;
|
||||||
|
if (!fcgi_extract_body(resp->stdout_data, resp->stdout_len, &body, &body_len)) {
|
||||||
|
body = resp->stdout_data;
|
||||||
|
body_len = resp->stdout_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 检查 FastCGI 响应是否已经包含完整的 HTTP 头 */
|
||||||
|
bool has_http_headers = false;
|
||||||
|
if (resp->stdout_len > 8) {
|
||||||
|
/* 检查是否以 HTTP/ 开头 */
|
||||||
|
if (strncmp(resp->stdout_data, "HTTP/", 5) == 0) {
|
||||||
|
has_http_headers = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_http_headers) {
|
||||||
|
/* 直接转发完整的 HTTP 响应(包含状态行和头) */
|
||||||
|
send_all(client_fd, resp->stdout_data, resp->stdout_len);
|
||||||
|
return keep_alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 检查 stdout 是否包含原始头(以 Status: 或 Content-Type: 开头) */
|
||||||
|
const char *raw_headers = resp->stdout_data;
|
||||||
|
size_t headers_len = body - resp->stdout_data;
|
||||||
|
|
||||||
|
if (headers_len > 0 && strncasecmp(raw_headers, "Content-Type:", 13) == 0) {
|
||||||
|
/* 原始头以 Content-Type 开头,构造状态行 */
|
||||||
|
char status_line[256];
|
||||||
|
int n = snprintf(status_line, sizeof(status_line),
|
||||||
|
"HTTP/1.1 %d OK\r\n", status);
|
||||||
|
send_all(client_fd, status_line, (size_t)n);
|
||||||
|
send_all(client_fd, raw_headers, headers_len);
|
||||||
|
send_all(client_fd, "\r\n", 2);
|
||||||
|
if (body_len > 0) {
|
||||||
|
send_all(client_fd, body, body_len);
|
||||||
|
}
|
||||||
|
} else if (headers_len > 0 && strncasecmp(raw_headers, "Status:", 7) == 0) {
|
||||||
|
/* 有 Status: 头,跳过它,用提取出的状态码 */
|
||||||
|
char status_line[256];
|
||||||
|
int n = snprintf(status_line, sizeof(status_line),
|
||||||
|
"HTTP/1.1 %d OK\r\n", status);
|
||||||
|
send_all(client_fd, status_line, (size_t)n);
|
||||||
|
|
||||||
|
/* 跳过 Status 行,发送其余头部 */
|
||||||
|
const char *p = raw_headers;
|
||||||
|
const char *end = raw_headers + headers_len;
|
||||||
|
while (p < end) {
|
||||||
|
const char *line_end = memchr(p, '\n', end - p);
|
||||||
|
if (!line_end) line_end = end;
|
||||||
|
if (strncasecmp(p, "Status:", 7) != 0) {
|
||||||
|
send_all(client_fd, p, (size_t)(line_end - p + 1));
|
||||||
|
}
|
||||||
|
p = line_end + 1;
|
||||||
|
}
|
||||||
|
send_all(client_fd, "\r\n", 2);
|
||||||
|
if (body_len > 0) {
|
||||||
|
send_all(client_fd, body, body_len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* 无额外头,构造标准响应 */
|
||||||
|
char header[512];
|
||||||
|
int n = snprintf(header, sizeof(header),
|
||||||
|
"HTTP/1.1 %d OK\r\n"
|
||||||
|
"Content-Type: text/html\r\n"
|
||||||
|
"Content-Length: %zu\r\n"
|
||||||
|
"Connection: %s\r\n"
|
||||||
|
"Server: Cocoon/1.0\r\n"
|
||||||
|
"\r\n",
|
||||||
|
status, body_len,
|
||||||
|
keep_alive ? "keep-alive" : "close");
|
||||||
|
send_all(client_fd, header, (size_t)n);
|
||||||
|
if (body_len > 0) {
|
||||||
|
send_all(client_fd, body, body_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keep_alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 公共 API ========== */
|
||||||
|
|
||||||
|
bool fcgi_handler_init(cocoon_fcgi_config_t *cfg, const cocoon_config_t *config) {
|
||||||
|
if (!cfg || !config) return false;
|
||||||
|
memset(cfg, 0, sizeof(*cfg));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < config->num_fastcgi; i++) {
|
||||||
|
if (cfg->count >= COCOON_MAX_FASTCGI_RULES) break;
|
||||||
|
|
||||||
|
cocoon_fcgi_rule_t *rule = &cfg->rules[cfg->count];
|
||||||
|
strncpy(rule->prefix, config->fastcgi[i].prefix, sizeof(rule->prefix) - 1);
|
||||||
|
rule->prefix[sizeof(rule->prefix) - 1] = '\0';
|
||||||
|
|
||||||
|
/* 初始化后端配置 */
|
||||||
|
fcgi_backend_t *be = &rule->backend;
|
||||||
|
memset(be, 0, sizeof(*be));
|
||||||
|
strncpy(be->host, config->fastcgi[i].host, sizeof(be->host) - 1);
|
||||||
|
be->host[sizeof(be->host) - 1] = '\0';
|
||||||
|
be->port = config->fastcgi[i].port;
|
||||||
|
be->is_unix_socket = config->fastcgi[i].is_unix_socket;
|
||||||
|
be->max_conns = config->fastcgi[i].pool_size > 0 ? config->fastcgi[i].pool_size : 4;
|
||||||
|
be->timeout_ms = config->fastcgi[i].timeout_ms > 0 ? config->fastcgi[i].timeout_ms : 30000;
|
||||||
|
|
||||||
|
/* 初始化连接池 */
|
||||||
|
if (!fcgi_pool_init(&rule->pool, be, be->max_conns)) {
|
||||||
|
log_warn("FastCGI: 连接池初始化失败 %s", be->host);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("FastCGI: 规则 #%zu %s -> %s:%d (pool=%d)",
|
||||||
|
cfg->count, rule->prefix, be->host, be->port, be->max_conns);
|
||||||
|
cfg->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fcgi_handler_destroy(cocoon_fcgi_config_t *cfg) {
|
||||||
|
if (!cfg) return;
|
||||||
|
for (size_t i = 0; i < cfg->count; i++) {
|
||||||
|
fcgi_pool_destroy(&cfg->rules[i].pool);
|
||||||
|
}
|
||||||
|
cfg->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cocoon_fcgi_rule_t *fcgi_handler_match(cocoon_fcgi_config_t *cfg, const char *path) {
|
||||||
|
if (!cfg || !path) return NULL;
|
||||||
|
for (size_t i = 0; i < cfg->count; i++) {
|
||||||
|
if (strncmp(path, cfg->rules[i].prefix, strlen(cfg->rules[i].prefix)) == 0) {
|
||||||
|
return &cfg->rules[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fcgi_handler_forward(cocoon_socket_t client_fd, const http_request_t *req,
|
||||||
|
cocoon_fcgi_rule_t *rule) {
|
||||||
|
if (!req || !rule) return false;
|
||||||
|
|
||||||
|
/* 构建 FastCGI 请求 */
|
||||||
|
fcgi_request_t fcgi_req;
|
||||||
|
if (!fcgi_request_init(&fcgi_req, 1)) {
|
||||||
|
static_send_error(client_fd, 502, req->keep_alive);
|
||||||
|
return req->keep_alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 构建 CGI 参数 */
|
||||||
|
const char *path_info = req->path + strlen(rule->prefix);
|
||||||
|
if (!path_info || path_info[0] == '\0') path_info = "";
|
||||||
|
if (!build_cgi_params(&fcgi_req, req, rule->prefix, path_info)) {
|
||||||
|
fcgi_request_free(&fcgi_req);
|
||||||
|
static_send_error(client_fd, 502, req->keep_alive);
|
||||||
|
return req->keep_alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发送请求并接收响应 */
|
||||||
|
fcgi_response_t resp;
|
||||||
|
memset(&resp, 0, sizeof(resp));
|
||||||
|
|
||||||
|
bool ok = fcgi_request(&rule->pool, &fcgi_req,
|
||||||
|
(const uint8_t *)req->body, req->body_len,
|
||||||
|
&resp);
|
||||||
|
|
||||||
|
fcgi_request_free(&fcgi_req);
|
||||||
|
|
||||||
|
if (!ok || !resp.complete) {
|
||||||
|
fcgi_response_free(&resp);
|
||||||
|
static_send_error(client_fd, 502, req->keep_alive);
|
||||||
|
return req->keep_alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发送响应给客户端 */
|
||||||
|
bool keep = send_http_response(client_fd, &resp, req->keep_alive);
|
||||||
|
fcgi_response_free(&resp);
|
||||||
|
|
||||||
|
return keep;
|
||||||
|
}
|
||||||
81
fcgi_handler.h
Normal file
81
fcgi_handler.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* fcgi_handler.h - FastCGI 服务器集成模块
|
||||||
|
*
|
||||||
|
* 将 FastCGI 协议接入 Cocoon 请求处理流程,
|
||||||
|
* 支持路径前缀匹配到 FastCGI 后端(PHP-FPM 等)。
|
||||||
|
*
|
||||||
|
* @author xfy
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COCOON_FCGI_HANDLER_H
|
||||||
|
#define COCOON_FCGI_HANDLER_H
|
||||||
|
|
||||||
|
#include "cocoon.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "fastcgi.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_fcgi_rule_t - FastCGI 路由规则
|
||||||
|
*
|
||||||
|
* 路径前缀匹配 + 后端连接池。
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char prefix[256]; /**< 路径前缀(如 "/api.php") */
|
||||||
|
fcgi_pool_t pool; /**< 后端连接池 */
|
||||||
|
fcgi_backend_t backend; /**< 后端配置(内嵌在规则中) */
|
||||||
|
} cocoon_fcgi_rule_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_fcgi_config_t - FastCGI 配置集合
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
cocoon_fcgi_rule_t rules[COCOON_MAX_FASTCGI_RULES];
|
||||||
|
size_t count;
|
||||||
|
} cocoon_fcgi_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fcgi_handler_init - 初始化 FastCGI 处理器
|
||||||
|
*
|
||||||
|
* 根据服务器配置创建连接池。
|
||||||
|
*
|
||||||
|
* @param cfg 输出配置
|
||||||
|
* @param config 服务器配置(含 fastcgi 数组)
|
||||||
|
* @return true 成功
|
||||||
|
*/
|
||||||
|
bool fcgi_handler_init(cocoon_fcgi_config_t *cfg, const cocoon_config_t *config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fcgi_handler_destroy - 销毁 FastCGI 处理器
|
||||||
|
*
|
||||||
|
* 关闭所有连接池。
|
||||||
|
*
|
||||||
|
* @param cfg FastCGI 配置
|
||||||
|
*/
|
||||||
|
void fcgi_handler_destroy(cocoon_fcgi_config_t *cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fcgi_handler_match - 匹配路径到 FastCGI 规则
|
||||||
|
*
|
||||||
|
* @param cfg FastCGI 配置
|
||||||
|
* @param path 请求路径
|
||||||
|
* @return 匹配的规则,未匹配返回 NULL
|
||||||
|
*/
|
||||||
|
cocoon_fcgi_rule_t *fcgi_handler_match(cocoon_fcgi_config_t *cfg, const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fcgi_handler_forward - 将 HTTP 请求转发到 FastCGI 后端
|
||||||
|
*
|
||||||
|
* 构建 CGI 环境变量,通过 FastCGI 连接池发送请求,
|
||||||
|
* 将后端响应(含 HTTP 头)回写给客户端。
|
||||||
|
*
|
||||||
|
* @param client_fd 客户端 socket
|
||||||
|
* @param req HTTP 请求
|
||||||
|
* @param rule FastCGI 规则
|
||||||
|
* @return true 保持连接,false 关闭
|
||||||
|
*/
|
||||||
|
bool fcgi_handler_forward(cocoon_socket_t client_fd, const http_request_t *req,
|
||||||
|
cocoon_fcgi_rule_t *rule);
|
||||||
|
|
||||||
|
#endif /* COCOON_FCGI_HANDLER_H */
|
||||||
27
server.c
27
server.c
@ -32,6 +32,7 @@
|
|||||||
#include "healthcheck.h"
|
#include "healthcheck.h"
|
||||||
#include "sse.h"
|
#include "sse.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "fcgi_handler.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -77,10 +78,10 @@ struct server_context {
|
|||||||
volatile int running; /**< 运行标志 */
|
volatile int running; /**< 运行标志 */
|
||||||
coco_sched_t *sched; /**< 协程调度器 */
|
coco_sched_t *sched; /**< 协程调度器 */
|
||||||
const char *config_file_path; /**< 配置文件路径(用于热重载) */
|
const char *config_file_path; /**< 配置文件路径(用于热重载) */
|
||||||
|
/* FastCGI 配置 */
|
||||||
|
cocoon_fcgi_config_t fcgi_config;
|
||||||
volatile int reload_requested; /**< 配置热重载请求标志 */
|
volatile int reload_requested; /**< 配置热重载请求标志 */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 全局活跃连接计数器(线程安全) */
|
|
||||||
static atomic_int g_active_connections = 0;
|
static atomic_int g_active_connections = 0;
|
||||||
/* 服务器启动时间 */
|
/* 服务器启动时间 */
|
||||||
static time_t g_server_start_time = 0;
|
static time_t g_server_start_time = 0;
|
||||||
@ -514,6 +515,20 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FastCGI 检查 */
|
||||||
|
if (conn->ctx && conn->ctx->fcgi_config.count > 0) {
|
||||||
|
cocoon_fcgi_rule_t *fcgi_rule = fcgi_handler_match(&conn->ctx->fcgi_config, req.path);
|
||||||
|
if (fcgi_rule) {
|
||||||
|
bool keep = fcgi_handler_forward(conn->fd, &req, fcgi_rule);
|
||||||
|
conn->response_status = 200; /* FastCGI 响应状态由后端决定 */
|
||||||
|
update_metrics(conn->response_status);
|
||||||
|
access_log_write((struct sockaddr *)&conn->client_addr, conn->addr_len,
|
||||||
|
&req, conn->response_status, -1);
|
||||||
|
http_request_free(&req);
|
||||||
|
return keep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 只支持 GET 和 HEAD */
|
/* 只支持 GET 和 HEAD */
|
||||||
if (req.method != HTTP_GET && req.method != HTTP_HEAD) {
|
if (req.method != HTTP_GET && req.method != HTTP_HEAD) {
|
||||||
conn->response_status = 405;
|
conn->response_status = 405;
|
||||||
@ -1373,6 +1388,11 @@ server_context_t *server_create(const cocoon_config_t *config, const char *confi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 初始化 FastCGI 处理器 */
|
||||||
|
if (!fcgi_handler_init(&ctx->fcgi_config, &ctx->config)) {
|
||||||
|
log_warn("FastCGI 初始化失败,FastCGI 路由不可用");
|
||||||
|
}
|
||||||
|
|
||||||
/* 启动主动健康检查 */
|
/* 启动主动健康检查 */
|
||||||
healthcheck_manager_init(&ctx->hc_manager);
|
healthcheck_manager_init(&ctx->hc_manager);
|
||||||
healthcheck_start(&ctx->hc_manager, &ctx->proxy_config, ctx->config.timeout_ms);
|
healthcheck_start(&ctx->hc_manager, &ctx->proxy_config, ctx->config.timeout_ms);
|
||||||
@ -1589,6 +1609,9 @@ void server_destroy(server_context_t *ctx) {
|
|||||||
/* 关闭反向代理连接池 */
|
/* 关闭反向代理连接池 */
|
||||||
proxy_config_destroy(&ctx->proxy_config);
|
proxy_config_destroy(&ctx->proxy_config);
|
||||||
|
|
||||||
|
/* 关闭 FastCGI 连接池 */
|
||||||
|
fcgi_handler_destroy(&ctx->fcgi_config);
|
||||||
|
|
||||||
/* 卸载插件 */
|
/* 卸载插件 */
|
||||||
cocoon_plugin_unload_all();
|
cocoon_plugin_unload_all();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user