cocoon/fcgi_handler.c
xfy911 a09452b43c 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 服务器集成
2026-06-15 09:28:21 +08:00

281 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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;
}