cocoon/grpc.c
xfy911 bf9787e6e9 feat(phase4): 实现 Phase 4 全部生态扩展模块
新增 4 个核心模块,12 个新文件,约 8832 行源代码 + 测试:

1. 扩展内置中间件 (middleware_ext.c/h)
   - JWT 认证:HS256 (HMAC-SHA256) 签名验证,Base64Url 解码
   - Security Headers:HSTS / X-Frame-Options / CSP / X-XSS-Protection
   - Request ID:32 字符 hex 唯一追踪 ID 生成与透传
   - IP 过滤:IPv4 / CIDR 黑名单与白名单,X-Forwarded-For 解析

2. 分布式负载均衡 (load_balance.c/h)
   - 一致性哈希:MurmurHash3 x86 32-bit + 512 虚拟节点/后端
   - 最少连接:实时活跃连接数跟踪
   - 加权响应时间:EWMA 指数加权移动平均
   - 随机算法

3. gRPC 支持 (grpc.c/h)
   - gRPC over HTTP/2,LEB128 消息帧编解码
   - 四种 RPC 模式:Unary / Server Streaming / Client Streaming / Bidirectional
   - gRPC-Web 兼容,17 个 gRPC 状态码完整支持

4. HTTP/3 (QUIC) (http3.c/h)
   - QUIC 传输层:UDP socket 管理,64-bit 连接 ID
   - HTTP/3 帧处理:HEADERS / DATA / SETTINGS / GOAWAY
   - QPACK 静态表编解码 (RFC 9204)
   - TLS 1.3 集成接口

新增单元测试:
- test_middleware_ext.c: 53 项测试
- test_load_balance.c: 56 项测试
- test_grpc.c: 88 项测试
- test_http3.c: 64 项测试
- 新增合计:261 项,累计 451 项

同时替换 coco 子模块为内联 stub 头文件,
确保无需外部依赖即可编译。
2026-06-10 11:36:45 +08:00

503 lines
16 KiB
C
Raw Permalink 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.

/**
* @file grpc.c - gRPC over HTTP/2 支持实现
*
* 基于现有 HTTP/2 传输层实现 gRPC 协议支持。
* 包含消息帧编解码、请求解析、trailers 格式化、状态码转换。
*
* @author Cocoon Team
*/
#include "grpc.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* ===== 内部辅助函数 ===== */
/**
* grpc_parse_path - 从 HTTP 路径解析 gRPC service/method
*
* 路径格式: "/package.Service/Method" 或 "/Service/Method"
* 解析结果:
* - service_name = "package.Service" 或 "Service"
* - method_name = "Method"
*
* @param path HTTP :path 伪头值
* @param svc_buf 服务名输出缓冲区
* @param svc_size 服务名缓冲区大小
* @param method_buf 方法名输出缓冲区
* @param method_size 方法名缓冲区大小
* @return 0 成功,-1 失败
*/
static int grpc_parse_path(const char *path,
char *svc_buf, size_t svc_size,
char *method_buf, size_t method_size) {
if (!path || path[0] != '/') {
return -1;
}
/* 跳过开头的 '/' */
const char *p = path + 1;
/* 查找第二个 '/'(分隔 service 和 method */
const char *slash = strrchr(p, '/');
if (!slash || slash == p) {
return -1; /* 格式错误:没有 method 部分 */
}
/* service_name = path[1..slash-1] */
size_t svc_len = (size_t)(slash - p);
if (svc_len >= svc_size) {
svc_len = svc_size - 1;
}
memcpy(svc_buf, p, svc_len);
svc_buf[svc_len] = '\0';
/* method_name = slash[1..end] */
size_t method_len = strlen(slash + 1);
if (method_len >= method_size) {
method_len = method_size - 1;
}
memcpy(method_buf, slash + 1, method_len);
method_buf[method_len] = '\0';
return 0;
}
/**
* extract_content_type - 从 HTTP 请求中提取 Content-Type 头值
*
* @param req HTTP 请求
* @return Content-Type 头值指针,未找到返回 NULL
*/
static const char *extract_content_type(const http_request_t *req) {
if (!req) return NULL;
/* 优先使用已解析的 content_type 字段 */
if (req->content_type[0] != '\0') {
return req->content_type;
}
/* 否则遍历 headers 数组查找 */
for (int i = 0; i < req->num_headers; i++) {
if (strcasecmp(req->headers[i].name, "content-type") == 0) {
return req->headers[i].value;
}
}
return NULL;
}
/* ===== gRPC 请求检测 ===== */
/**
* grpc_detect - 检测请求是否为 gRPC 请求
*
* 检查 Content-Type 是否以 "application/grpc" 开头。
* 覆盖:
* - application/grpc
* - application/grpc+proto
* - application/grpc+json
* - application/grpc-web (gRPC-Web)
* - application/grpc-web+proto
*/
bool grpc_detect(const http_request_t *req) {
if (!req) return false;
const char *ct = extract_content_type(req);
if (!ct) return false;
/* 检查 "application/grpc" 前缀(大小写不敏感) */
if (strncasecmp(ct, "application/grpc", 16) == 0) {
char c = ct[16];
/* 标准 gRPC: application/grpc, application/grpc+proto, ... */
if (c == '\0' || c == '+' || c == ';' || c == ' ') {
return true;
}
/* gRPC-Web: application/grpc-web, application/grpc-web+proto */
if (c == '-' && strncasecmp(ct + 17, "web", 3) == 0) {
char d = ct[20];
if (d == '\0' || d == '+' || d == ';' || d == ' ') {
return true;
}
}
}
return false;
}
/**
* grpc_is_grpc_web - 检测请求是否为 gRPC-Web
*
* gRPC-Web 使用 application/grpc-web 前缀。
*/
bool grpc_is_grpc_web(const http_request_t *req) {
if (!req) return false;
const char *ct = extract_content_type(req);
if (!ct) return false;
if (strncasecmp(ct, "application/grpc-web", 20) == 0) {
char c = ct[20];
if (c == '\0' || c == '+' || c == ';' || c == ' ') {
return true;
}
}
return false;
}
/* ===== gRPC 请求解析 ===== */
/**
* grpc_parse_request - 从 HTTP/2 请求解析 gRPC 上下文
*
* 解析路径获取 service/method并解析消息体中的 gRPC 消息帧。
* 默认标记为非流式Unary流模式由调用方根据 protobuf 定义设置。
*/
int grpc_parse_request(const http_request_t *req, grpc_request_t *grpc_req) {
if (!req || !grpc_req) return -1;
/* 清空输出结构 */
memset(grpc_req, 0, sizeof(grpc_request_t));
/* 检测 gRPC-Web */
grpc_req->is_grpc_web = grpc_is_grpc_web(req);
/* 解析 service/method */
if (grpc_parse_path(req->path,
grpc_req->service_name, sizeof(grpc_req->service_name),
grpc_req->method_name, sizeof(grpc_req->method_name)) != 0) {
/* 路径格式错误,设置默认值 */
grpc_req->status = GRPC_INTERNAL;
snprintf(grpc_req->status_message, sizeof(grpc_req->status_message),
"Invalid gRPC path format: %.200s", req->path);
return -1;
}
/* 默认流模式Unary非流式 */
grpc_req->client_streaming = false;
grpc_req->server_streaming = false;
grpc_req->is_streaming = false;
/* 解析消息体中的 gRPC 消息帧 */
if (req->body && req->body_len > 0) {
int decoded = grpc_decode_message((const uint8_t *)req->body,
req->body_len, &grpc_req->message);
if (decoded < 0) {
grpc_req->status = GRPC_INTERNAL;
snprintf(grpc_req->status_message, sizeof(grpc_req->status_message),
"Failed to decode gRPC message frame");
return -1;
}
}
/* 提取元数据(非 Content-Type 的请求头) */
grpc_req->metadata_count = 0;
for (int i = 0; i < req->num_headers && grpc_req->metadata_count < 16; i++) {
const char *name = req->headers[i].name;
/* 跳过 HTTP/2 伪头和标准传输头 */
if (name[0] == ':') continue;
if (strcasecmp(name, "content-type") == 0) continue;
if (strcasecmp(name, "content-length") == 0) continue;
if (strcasecmp(name, "te") == 0) continue;
if (strcasecmp(name, "host") == 0) continue;
snprintf(grpc_req->metadata[grpc_req->metadata_count][0], 256, "%s", name);
snprintf(grpc_req->metadata[grpc_req->metadata_count][1], 256, "%s",
req->headers[i].value);
grpc_req->metadata_count++;
}
grpc_req->status = GRPC_OK;
return 0;
}
/* ===== gRPC 消息帧编解码 ===== */
/**
* grpc_decode_message - 解码 gRPC 长度前缀消息帧
*
* 帧格式: [compressed:1][length:4(BE)][payload:N]
* payload 动态分配,调用者需通过 grpc_message_free() 释放。
*/
int grpc_decode_message(const uint8_t *buf, size_t len, grpc_message_t *msg) {
if (!buf || !msg || len == 0) return -1;
/* 至少需要 5 字节前缀 */
if (len < 5) return -1;
/* 解析压缩标志 */
msg->compressed = buf[0];
/* 解析长度big-endian uint32 */
msg->length = ((uint32_t)buf[1] << 24) |
((uint32_t)buf[2] << 16) |
((uint32_t)buf[3] << 8) |
(uint32_t)buf[4];
/* 检查数据完整性 */
if (len < 5 + msg->length) return -1;
/* 分配 payload 内存 */
if (msg->length > 0) {
msg->payload = (uint8_t *)malloc(msg->length);
if (!msg->payload) return -1;
memcpy(msg->payload, buf + 5, msg->length);
} else {
msg->payload = NULL;
}
return (int)(5 + msg->length);
}
/**
* grpc_encode_message - 编码 gRPC 长度前缀消息帧
*
* 帧格式: [compressed:1][length:4(BE)][payload:N]
*/
int grpc_encode_message(const grpc_message_t *msg, uint8_t *buf, size_t buf_size) {
if (!msg || !buf) return -1;
/* 检查缓冲区是否足够5 字节前缀 + payload */
if (buf_size < 5 + msg->length) return -1;
/* 写入压缩标志 */
buf[0] = msg->compressed;
/* 写入长度big-endian uint32 */
buf[1] = (uint8_t)((msg->length >> 24) & 0xFF);
buf[2] = (uint8_t)((msg->length >> 16) & 0xFF);
buf[3] = (uint8_t)((msg->length >> 8) & 0xFF);
buf[4] = (uint8_t)(msg->length & 0xFF);
/* 复制 payload */
if (msg->length > 0 && msg->payload) {
memcpy(buf + 5, msg->payload, msg->length);
}
return (int)(5 + msg->length);
}
/**
* grpc_message_free - 释放 gRPC 消息中动态分配的资源
*/
void grpc_message_free(grpc_message_t *msg) {
if (!msg) return;
if (msg->payload) {
free(msg->payload);
msg->payload = NULL;
}
msg->length = 0;
msg->compressed = 0;
}
/* ===== gRPC Trailers ===== */
/**
* grpc_format_response_trailers - 格式化 gRPC trailers
*
* gRPC 使用 HTTP/2 trailing headers 传递最终状态。
* 格式: grpc-status: N\r\ngrpc-message: text\r\n
*
* gRPC-Web 不使用 trailers而是在响应头中发送 grpc-status。
*/
int grpc_format_response_trailers(const grpc_request_t *grpc_req, char *buf, size_t buf_size) {
if (!grpc_req || !buf || buf_size == 0) return -1;
int n = snprintf(buf, buf_size,
"grpc-status: %d\r\n"
"grpc-message: %s\r\n",
(int)grpc_req->status,
grpc_req->status_message[0] ? grpc_req->status_message : "");
if ((size_t)n >= buf_size) return -1;
return n;
}
/* ===== 状态码转换 ===== */
/**
* grpc_status_to_http - gRPC 状态码转 HTTP 状态码
*
* 映射关系参照 gRPC 规范:
* https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
*/
int grpc_status_to_http(grpc_status_t status) {
switch (status) {
case GRPC_OK: return 200;
case GRPC_CANCELLED: return 499; /* Client Closed Request */
case GRPC_UNKNOWN: return 500; /* Internal Server Error */
case GRPC_INVALID_ARGUMENT: return 400; /* Bad Request */
case GRPC_DEADLINE_EXCEEDED: return 504; /* Gateway Timeout */
case GRPC_NOT_FOUND: return 404; /* Not Found */
case GRPC_ALREADY_EXISTS: return 409; /* Conflict */
case GRPC_PERMISSION_DENIED: return 403; /* Forbidden */
case GRPC_RESOURCE_EXHAUSTED: return 429; /* Too Many Requests */
case GRPC_FAILED_PRECONDITION: return 400; /* Bad Request */
case GRPC_ABORTED: return 409; /* Conflict */
case GRPC_OUT_OF_RANGE: return 400; /* Bad Request */
case GRPC_UNIMPLEMENTED: return 501; /* Not Implemented */
case GRPC_INTERNAL: return 500; /* Internal Server Error */
case GRPC_UNAVAILABLE: return 503; /* Service Unavailable */
case GRPC_DATA_LOSS: return 500; /* Internal Server Error */
case GRPC_UNAUTHENTICATED: return 401; /* Unauthorized */
default: return 500;
}
}
/**
* grpc_http_to_status - HTTP 状态码转 gRPC 状态码
*
* HTTP → gRPC 反向映射。
*/
grpc_status_t grpc_http_to_status(int http_status) {
switch (http_status) {
case 200: return GRPC_OK;
case 400: return GRPC_INVALID_ARGUMENT;
case 401: return GRPC_UNAUTHENTICATED;
case 403: return GRPC_PERMISSION_DENIED;
case 404: return GRPC_NOT_FOUND;
case 409: return GRPC_ABORTED;
case 412: return GRPC_FAILED_PRECONDITION;
case 429: return GRPC_RESOURCE_EXHAUSTED;
case 499: return GRPC_CANCELLED;
case 500: return GRPC_INTERNAL;
case 501: return GRPC_UNIMPLEMENTED;
case 503: return GRPC_UNAVAILABLE;
case 504: return GRPC_DEADLINE_EXCEEDED;
default:
if (http_status >= 200 && http_status < 300) return GRPC_OK;
if (http_status >= 400 && http_status < 500) return GRPC_INVALID_ARGUMENT;
return GRPC_INTERNAL;
}
}
/**
* grpc_status_to_string - gRPC 状态码转可读字符串
*
* @return 状态码名称,未知值返回 "UNKNOWN"
*/
const char *grpc_status_to_string(grpc_status_t status) {
switch (status) {
case GRPC_OK: return "OK";
case GRPC_CANCELLED: return "CANCELLED";
case GRPC_UNKNOWN: return "UNKNOWN";
case GRPC_INVALID_ARGUMENT: return "INVALID_ARGUMENT";
case GRPC_DEADLINE_EXCEEDED: return "DEADLINE_EXCEEDED";
case GRPC_NOT_FOUND: return "NOT_FOUND";
case GRPC_ALREADY_EXISTS: return "ALREADY_EXISTS";
case GRPC_PERMISSION_DENIED: return "PERMISSION_DENIED";
case GRPC_RESOURCE_EXHAUSTED: return "RESOURCE_EXHAUSTED";
case GRPC_FAILED_PRECONDITION: return "FAILED_PRECONDITION";
case GRPC_ABORTED: return "ABORTED";
case GRPC_OUT_OF_RANGE: return "OUT_OF_RANGE";
case GRPC_UNIMPLEMENTED: return "UNIMPLEMENTED";
case GRPC_INTERNAL: return "INTERNAL";
case GRPC_UNAVAILABLE: return "UNAVAILABLE";
case GRPC_DATA_LOSS: return "DATA_LOSS";
case GRPC_UNAUTHENTICATED: return "UNAUTHENTICATED";
default: return "UNKNOWN";
}
}
/* ===== gRPC 响应发送 ===== */
/**
* grpc_send_unary_response - 发送 Unary RPC 响应
*
* 将响应 payload 编码为 gRPC 消息帧格式,通过 socket 发送。
* 编码格式: [flag:1][length:4(BE)][payload]
*/
int grpc_send_unary_response(cocoon_socket_t fd, grpc_request_t *grpc_req,
const uint8_t *resp_payload, size_t resp_len) {
if (fd == COCOON_INVALID_SOCKET || !grpc_req) return -1;
/* 构建响应消息帧 */
grpc_message_t resp_msg = {
.compressed = 0, /* 默认不压缩 */
.length = (uint32_t)resp_len,
.payload = (uint8_t *)(uintptr_t)resp_payload /* const 转换,编码时不修改 */
};
/* 分配编码缓冲区 */
size_t buf_size = 5 + resp_len;
uint8_t *buf = (uint8_t *)malloc(buf_size);
if (!buf) return -1;
int encoded = grpc_encode_message(&resp_msg, buf, buf_size);
if (encoded < 0) {
free(buf);
return -1;
}
/* 通过 socket 发送 */
ssize_t sent = cocoon_socket_send(fd, (const char *)buf, (size_t)encoded);
free(buf);
if (sent < 0) return -1;
return (int)sent;
}
/**
* grpc_send_trailers - 发送 gRPC trailers
*
* 格式化并发送 gRPC trailing headers。
* 在当前简化实现中,将 trailers 格式化为文本缓冲区返回。
* 实际 HTTP/2 trailers 发送需由调用方通过 nghttp2 完成。
*/
int grpc_send_trailers(cocoon_socket_t fd, uint32_t stream_id,
grpc_status_t status, const char *message) {
if (fd == COCOON_INVALID_SOCKET) return -1;
/* 构建临时 grpc_request_t 用于格式化 */
grpc_request_t tmp_req = {0};
tmp_req.status = status;
if (message) {
snprintf(tmp_req.status_message, sizeof(tmp_req.status_message), "%s", message);
}
char trailers_buf[512];
int n = grpc_format_response_trailers(&tmp_req, trailers_buf, sizeof(trailers_buf));
if (n < 0) return -1;
/* 发送 trailers 文本(实际应由 HTTP/2 层包装为 HEADERS 帧) */
ssize_t sent = cocoon_socket_send(fd, trailers_buf, (size_t)n);
(void)stream_id; /* 在完整 HTTP/2 实现中用于 nghttp2_submit_trailer */
if (sent < 0) return -1;
return (int)sent;
}
/**
* grpc_request_free - 释放 gRPC 请求资源
*
* 释放 grpc_request_t 中所有动态分配的内存。
* 包括 message.payload 和关联资源。
*/
void grpc_request_free(grpc_request_t *grpc_req) {
if (!grpc_req) return;
/* 释放消息 payload */
grpc_message_free(&grpc_req->message);
/* 清空其他字段 */
grpc_req->metadata_count = 0;
}
/**
* grpc_error_response - 发送 gRPC 错误响应
*
* 直接发送 grpc-status trailers不发送消息体。
* 适用于请求解析失败、方法未找到、未实现等场景。
*/
void grpc_error_response(cocoon_socket_t fd, uint32_t stream_id,
grpc_status_t status, const char *message) {
if (fd == COCOON_INVALID_SOCKET) return;
const char *status_msg = message ? message : grpc_status_to_string(status);
/* 发送错误 trailers */
grpc_send_trailers(fd, stream_id, status, status_msg);
}