cocoon/fastcgi.h
xfy911 57948d9dc1 feat: FastCGI 协议核心实现 + 17 项单元测试
- 新增 fastcgi.c/h:FastCGI 1.0 协议客户端实现
  - 记录编码/解码(BEGIN_REQUEST/PARAMS/STDIN/STDOUT/STDERR/END_REQUEST)
  - 名值对(NV)编码(支持短/长长度)
  - 参数管理(SCRIPT_NAME/REQUEST_METHOD 等 CGI 环境变量)
  - 响应解析与状态码提取(Status: 404 → 404)
  - 响应体提取(从 stdout 数据中提取 body)
  - 连接池(预创建连接、获取/归还)
  - 完整请求流程(发送 BeginRequest + Params + Stdin + 接收响应)

- 新增 tests/unit/test_fastcgi.c:17 项单元测试
  - 记录编码/解码
  - 名值对编码(短/长长度)
  - 参数管理
  - 响应解析
  - 状态码/Body 提取
  - 请求初始化

- 更新 Makefile:添加 fastcgi.c 编译规则和 test_fastcgi 测试规则
- 更新 .gitignore:修复 tests/unit/test_* 规则,确保 .c 源文件被跟踪

编译零警告,458 单元测试 + 115 集成测试全部通过 ✓
2026-06-15 08:28:57 +08:00

211 lines
6.4 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.

/*
* fastcgi.h - FastCGI 协议客户端实现
*
* 支持 FastCGI 1.0 协议 (RFC 未正式标准化,但广泛使用)
* 用于连接 PHP-FPM、uWSGI 等 FastCGI 后端
*/
#ifndef COCOON_FASTCGI_H
#define COCOON_FASTCGI_H
#include "cocoon.h"
#include <stdint.h>
#include <stdbool.h>
/* FastCGI 协议版本 */
#define FCGI_VERSION_1 1
/* FastCGI 消息类型 */
#define FCGI_BEGIN_REQUEST 1
#define FCGI_ABORT_REQUEST 2
#define FCGI_END_REQUEST 3
#define FCGI_PARAMS 4
#define FCGI_STDIN 5
#define FCGI_STDOUT 6
#define FCGI_STDERR 7
#define FCGI_DATA 8
#define FCGI_GET_VALUES 9
#define FCGI_GET_VALUES_RESULT 10
#define FCGI_UNKNOWN_TYPE 11
/* FastCGI 请求角色 */
#define FCGI_RESPONDER 1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER 3
/* FastCGI 标志 */
#define FCGI_KEEP_CONN 1
/* FastCGI 协议状态码 */
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
/* FastCGI 记录头大小 */
#define FCGI_HEADER_LEN 8
#define FCGI_MAX_CONTENT_LEN 65535
/* FastCGI 记录头 */
typedef struct {
uint8_t version;
uint8_t type;
uint16_t requestId;
uint16_t contentLength;
uint8_t paddingLength;
uint8_t reserved;
} fcgi_header_t;
/* FastCGI BeginRequest 体 */
typedef struct {
uint16_t role;
uint8_t flags;
uint8_t reserved[5];
} fcgi_begin_request_t;
/* FastCGI EndRequest 体 */
typedef struct {
uint32_t appStatus;
uint8_t protocolStatus;
uint8_t reserved[3];
} fcgi_end_request_t;
/* FastCGI 参数对(名值对) */
typedef struct fcgi_param {
char *name;
char *value;
struct fcgi_param *next;
} fcgi_param_t;
/* FastCGI 请求上下文 */
typedef struct {
uint16_t requestId;
int backend_fd; /* 到后端 FastCGI 服务器的连接 */
bool keep_conn; /* 是否保持连接 */
fcgi_param_t *params; /* 参数链表 */
uint16_t next_requestId; /* 下一个请求 ID连接复用 */
} fcgi_request_t;
/* FastCGI 响应解析状态 */
typedef enum {
FCGI_PARSE_HEADER, /* 解析记录头 */
FCGI_PARSE_CONTENT, /* 解析内容 */
FCGI_PARSE_PADDING, /* 解析填充 */
FCGI_PARSE_DONE /* 完成 */
} fcgi_parse_state_t;
/* FastCGI 响应 */
typedef struct {
int status; /* HTTP 状态码 */
char *stdout_data; /* stdout 数据 */
size_t stdout_len;
char *stderr_data; /* stderr 数据 */
size_t stderr_len;
bool complete; /* 是否收到 END_REQUEST */
uint32_t appStatus; /* FastCGI 应用状态码 */
uint8_t protocolStatus; /* FastCGI 协议状态码 */
} fcgi_response_t;
/* FastCGI 后端配置 */
typedef struct {
char *host; /* 主机或 Unix socket 路径 */
int port; /* 端口TCP 时有效) */
bool is_unix_socket; /* 是否为 Unix domain socket */
int max_conns; /* 最大连接数 */
int timeout_ms; /* 连接超时(毫秒) */
} fcgi_backend_t;
/* FastCGI 连接池项 */
typedef struct fcgi_pool_item {
int fd; /* 连接文件描述符 */
bool in_use; /* 是否正在使用 */
bool available; /* 是否可用(连接正常) */
uint16_t last_requestId; /* 最后使用的请求 ID */
struct fcgi_pool_item *next;
} fcgi_pool_item_t;
/* FastCGI 连接池 */
typedef struct {
fcgi_backend_t *backend;
fcgi_pool_item_t *pool; /* 连接链表 */
int pool_size; /* 当前连接数 */
int max_pool_size; /* 最大连接数 */
} fcgi_pool_t;
/* ========== 核心 API ========== */
/* 创建/销毁请求 */
bool fcgi_request_init(fcgi_request_t *req, uint16_t requestId);
void fcgi_request_free(fcgi_request_t *req);
/* 添加参数CGI 环境变量) */
bool fcgi_add_param(fcgi_request_t *req, const char *name, const char *value);
/* 构建 FastCGI 记录 */
int fcgi_build_record(uint8_t type, uint16_t requestId,
const uint8_t *data, uint16_t len,
uint8_t *out, size_t out_size);
/* 构建 BeginRequest 记录 */
int fcgi_build_begin_request(uint16_t requestId, uint16_t role,
uint8_t flags, uint8_t *out, size_t out_size);
/* 构建 Params 记录(编码所有参数) */
int fcgi_build_params(uint16_t requestId, const fcgi_param_t *params,
uint8_t *out, size_t out_size);
/* 构建空 Stdin 记录(表示输入结束) */
int fcgi_build_empty_stdin(uint16_t requestId, uint8_t *out, size_t out_size);
/* 解析响应 */
bool fcgi_parse_response(const uint8_t *data, size_t len,
fcgi_response_t *resp, size_t *consumed);
/* 解析响应记录(流式) */
bool fcgi_parse_record(const uint8_t *data, size_t len,
fcgi_header_t *header, const uint8_t **content,
size_t *consumed);
/* 提取 HTTP 状态码(从 stdout 的 Status 头) */
int fcgi_extract_status(const char *stdout_data, size_t len);
/* 提取 HTTP 响应体(跳过头部) */
bool fcgi_extract_body(const char *stdout_data, size_t len,
const char **body, size_t *body_len);
/* ========== 连接池 API ========== */
/* 初始化/销毁连接池 */
bool fcgi_pool_init(fcgi_pool_t *pool, fcgi_backend_t *backend, int max_conns);
void fcgi_pool_destroy(fcgi_pool_t *pool);
/* 获取连接 */
fcgi_pool_item_t *fcgi_pool_acquire(fcgi_pool_t *pool);
/* 释放连接(归还到池) */
void fcgi_pool_release(fcgi_pool_t *pool, fcgi_pool_item_t *item);
/* 连接到后端 */
bool fcgi_backend_connect(fcgi_backend_t *backend);
/* 发送完整请求 */
bool fcgi_send_request(int fd, const fcgi_request_t *req,
const uint8_t *body, size_t body_len);
/* 接收完整响应 */
bool fcgi_recv_response(int fd, fcgi_response_t *resp, int timeout_ms);
/* 清理响应 */
void fcgi_response_free(fcgi_response_t *resp);
/* 完整请求流程(连接池版) */
bool fcgi_request(fcgi_pool_t *pool, fcgi_request_t *req,
const uint8_t *body, size_t body_len,
fcgi_response_t *resp);
/* 实用函数:编码名值对长度 */
bool fcgi_encode_name_value_len(const char *name, const char *value,
uint8_t *out, size_t *out_len);
#endif /* COCOON_FASTCGI_H */