cocoon/http2.h
xfy911 7fe52a3b6e feat(http2): 添加 HTTP/2 反向代理支持
实现 HTTP/2 客户端连接通过 HTTP/2 流转发到 HTTP/1.1 后端:
- 在 http2.c 中新增 http2_serve_proxy() 函数
- 实现请求重组:将 HTTP/2 伪头(:method, :path, :authority)重组为 HTTP/1.1 请求
- 实现后端响应解析:解析 HTTP/1.1 响应头并通过 nghttp2 提交 HTTP/2 响应帧
- 过滤 hop-by-hop 头(Connection, Transfer-Encoding 等)
- 添加 X-Forwarded-For 头
- 支持 TLS 和非 TLS 后端连接
- 在 server.c 中注入代理配置到 HTTP/2 会话
- 修复 parse_http1_response 中 body_len 下溢问题
- 添加 HTTP/2 代理集成测试

当前限制:
- 采用非流式方案(先收集完整后端响应再发送)
- 连接池对 HTTP/1.0 后端(无 keep-alive)处理有待完善
- 单元测试:142 项全部通过
- 集成测试:HTTP/2 GET 代理测试通过
2026-06-09 09:39:08 +08:00

189 lines
5.2 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.

/**
* http2.h - HTTP/2 支持头文件
*
* 使用 nghttp2 库实现 HTTP/2 协议支持。
* 支持 TLS ALPN 协商和明文 h2c 升级。
*
* @author xfy
*/
#ifndef COCOON_HTTP2_H
#define COCOON_HTTP2_H
#include "cocoon.h"
#include "proxy.h"
#include "http.h"
#include <nghttp2/nghttp2.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* http2_session_t - HTTP/2 会话状态
*
* 每个启用了 HTTP/2 的连接拥有一个此结构体。
*/
typedef struct {
nghttp2_session *session; /**< nghttp2 会话对象 */
int fd; /**< 底层 socket fd */
bool tls_mode; /**< 是否通过 TLS ALPN 协商 */
struct http2_stream_data *streams; /**< 活跃的流列表(头节点) */
const char *root_dir; /**< 静态资源根目录 */
bool gzip_enabled; /**< 是否启用 gzip 压缩 */
bool brotli_enabled; /**< 是否启用 brotli 压缩 */
cocoon_proxy_config_t *proxy_config; /**< 反向代理配置 */
struct sockaddr_storage *client_addr; /**< 客户端地址 */
} http2_session_t;
/**
* http2_stream_data - HTTP/2 流级数据
*
* 每个请求流对应一个此结构体。
*/
typedef struct http2_stream_data {
struct http2_stream_data *next;
int32_t stream_id; /**< HTTP/2 流 ID */
http_request_t request; /**< 解析后的 HTTP 请求 */
bool request_complete; /**< 请求是否接收完整 */
int file_fd; /**< 响应文件 fd-1 表示无) */
char *response_body; /**< 响应体(用于动态内容) */
size_t response_len; /**< 响应体长度 */
size_t response_sent; /**< 已发送的响应体字节数 */
} http2_stream_data_t;
/**
* http2_init - 初始化 HTTP/2 全局状态
*
* 应在服务器启动前调用一次。
*
* @return 0 成功,-1 失败
*/
int http2_init(void);
/**
* http2_cleanup - 清理 HTTP/2 全局状态
*/
void http2_cleanup(void);
/**
* http2_session_create - 为连接创建 HTTP/2 会话
*
* @param fd socket 文件描述符
* @param tls_mode 是否通过 TLS ALPN 协商true=TLSfalse=h2c
* @return 新会话对象,失败返回 NULL
*/
http2_session_t *http2_session_create(int fd, bool tls_mode);
/**
* http2_session_destroy - 销毁 HTTP/2 会话
*
* @param h2 会话对象
*/
void http2_session_destroy(http2_session_t *h2);
/**
* http2_session_is_http2 - 判断连接是否已升级为 HTTP/2
*
* @param fd socket 文件描述符
* @return true 是 HTTP/2 连接
*/
bool http2_session_is_http2(int fd);
/**
* http2_session_get - 获取 fd 对应的 HTTP/2 会话
*
* @param fd socket 文件描述符
* @return 会话对象NULL 表示非 HTTP/2 连接
*/
http2_session_t *http2_session_get(int fd);
/**
* http2_recv - 接收并处理客户端数据
*
* 将读取的数据喂给 nghttp2 库,触发回调处理请求。
*
* @param h2 会话对象
* @param buf 接收缓冲区
* @param len 数据长度
* @return 0 成功,-1 错误(应关闭连接)
*/
int http2_recv(http2_session_t *h2, const uint8_t *buf, size_t len);
/**
* http2_send_pending - 发送挂起的 HTTP/2 帧
*
* 应在 socket 可写时调用。
*
* @param h2 会话对象
* @return 0 成功,-1 错误
*/
int http2_send_pending(http2_session_t *h2);
/**
* http2_want_read - 检查 nghttp2 是否还需要读取数据
*
* @param h2 会话对象
* @return true 需要继续读取
*/
bool http2_want_read(http2_session_t *h2);
/**
* http2_want_write - 检查 nghttp2 是否还需要写入数据
*
* @param h2 会话对象
* @return true 需要继续写入
*/
bool http2_want_write(http2_session_t *h2);
/**
* http2_on_connection_accepted - 新连接接受后的处理
*
* 对于 TLS 连接,在 TLS 握手完成后调用;
* 对于明文连接,在读取到客户端魔术字后调用。
*
* @param fd socket 文件描述符
* @param tls_mode 是否通过 TLS ALPN 协商
* @return 0 成功,-1 失败(应关闭连接)
*/
int http2_on_connection_accepted(int fd, bool tls_mode);
/**
* http2_session_upgrade - 处理 h2c 升级请求
*
* 注册升级流stream_id=1并提交静态文件响应。
* 调用前应先创建会话并发送服务器 SETTINGS 前言。
*
* @param h2 HTTP/2 会话
* @param req HTTP/1.1 升级请求(会被复制)
* @return 0 成功,-1 失败
*/
int http2_session_upgrade(http2_session_t *h2, const http_request_t *req);
/**
*
* @param proxy_config 反向代理配置NULL 表示不启用)
* @param client_addr 客户端地址NULL 表示不启用)
*/
void http2_session_set_proxy_config(http2_session_t *h2, cocoon_proxy_config_t *proxy_config, struct sockaddr_storage *client_addr);
/**
* http2_session_set_context - 设置会话的服务上下文
*
* 传入 root_dir 和压缩配置,供后续静态文件服务使用。
*
* @param h2 会话对象
* @param root_dir 静态资源根目录
* @param gzip_enabled 是否启用 gzip
* @param brotli_enabled 是否启用 brotli
*/
void http2_session_set_context(http2_session_t *h2, const char *root_dir, bool gzip_enabled, bool brotli_enabled);
#ifdef __cplusplus
}
#endif
#endif /* COCOON_HTTP2_H */