feat(http2): 实现 HTTP/2 POST 请求体支持
- on_data_chunk_recv_callback: 将 DATA 帧数据追加到 stream->request.body - on_frame_recv_callback: 根据请求方法选择处理方式(POST → http2_serve_post) - http2_serve_post: 处理 HTTP/2 POST 请求 - 支持 multipart/form-data 文件上传 - 支持 JSON 和 form-urlencoded 回显 - 构建 JSON 响应并通过 nghttp2 发送 - 包含 multipart.h 和 platform.h 到 http2.c - 142 个单元测试 + 77 项集成测试全部通过,编译零警告
This commit is contained in:
parent
4105aec916
commit
0bf37ba3f0
163
http2.c
163
http2.c
@ -11,6 +11,8 @@
|
||||
#include "static.h"
|
||||
#include "log.h"
|
||||
#include "tls.h"
|
||||
#include "multipart.h"
|
||||
#include "platform.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -145,6 +147,7 @@ static ssize_t http2_data_source_read_callback(nghttp2_session *session, int32_t
|
||||
static bool http2_serve_directory(http2_session_t *h2, http2_stream_data_t *stream,
|
||||
const char *real_path, const char *request_path);
|
||||
static void http2_serve_static(http2_session_t *h2, http2_stream_data_t *stream);
|
||||
static void http2_serve_post(http2_session_t *h2, http2_stream_data_t *stream);
|
||||
|
||||
/* ===================== 会话管理 ===================== */
|
||||
|
||||
@ -629,8 +632,13 @@ static int on_frame_recv_callback(nghttp2_session *session,
|
||||
}
|
||||
stream->request_complete = true;
|
||||
|
||||
/* 处理静态文件请求 */
|
||||
/* 根据请求方法选择处理方式 */
|
||||
if (stream->request.method == HTTP_POST) {
|
||||
http2_serve_post(h2, stream);
|
||||
} else {
|
||||
/* 处理静态文件请求(GET/HEAD/OPTIONS等) */
|
||||
http2_serve_static(h2, stream);
|
||||
}
|
||||
http2_send_pending(h2);
|
||||
}
|
||||
break;
|
||||
@ -701,9 +709,18 @@ static int on_data_chunk_recv_callback(nghttp2_session *session __attribute__((u
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: 将数据追加到请求体缓冲区 */
|
||||
(void)data;
|
||||
(void)len;
|
||||
/* 收集请求体数据 */
|
||||
if (stream->request.body_len + len > HTTP_MAX_BODY) {
|
||||
return 0; /* 超过最大限制,静默丢弃 */
|
||||
}
|
||||
char *new_body = (char *)realloc(stream->request.body, stream->request.body_len + len + 1);
|
||||
if (!new_body) {
|
||||
return 0; /* 内存不足,静默丢弃 */
|
||||
}
|
||||
memcpy(new_body + stream->request.body_len, data, len);
|
||||
stream->request.body_len += len;
|
||||
new_body[stream->request.body_len] = '\0';
|
||||
stream->request.body = new_body;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1004,6 +1021,144 @@ static bool http2_serve_directory(http2_session_t *h2, http2_stream_data_t *stre
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* http2_serve_post - 处理 HTTP/2 POST 请求
|
||||
*
|
||||
* 支持 multipart/form-data 文件上传、JSON 回显和表单回显。
|
||||
* 构建 JSON 响应并通过 nghttp2 发送。
|
||||
*
|
||||
* @param h2 会话指针
|
||||
* @param stream 流数据(含请求信息)
|
||||
*/
|
||||
static void http2_serve_post(http2_session_t *h2, http2_stream_data_t *stream) {
|
||||
char response[4096];
|
||||
int n = 0;
|
||||
|
||||
/* multipart/form-data 文件上传 */
|
||||
if (strstr(stream->request.content_type, "multipart/form-data") != NULL) {
|
||||
char boundary[256];
|
||||
if (!multipart_extract_boundary(stream->request.content_type, boundary, sizeof(boundary))) {
|
||||
nghttp2_nv hdrs[] = {
|
||||
{(uint8_t *)":status", (uint8_t *)"400", 7, 3, 0}};
|
||||
nghttp2_submit_response(h2->session, stream->stream_id, hdrs, 1, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
multipart_part_t *parts = NULL;
|
||||
int num_parts = 0;
|
||||
if (multipart_parse(stream->request.body, stream->request.body_len, boundary, &parts, &num_parts) != 0) {
|
||||
nghttp2_nv hdrs[] = {
|
||||
{(uint8_t *)":status", (uint8_t *)"400", 7, 3, 0}};
|
||||
nghttp2_submit_response(h2->session, stream->stream_id, hdrs, 1, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
n += snprintf(response + n, sizeof(response) - n,
|
||||
"{\"method\":\"%s\",\"path\":\"%s\",\"uploaded\":%d,\"files\":[",
|
||||
http_method_str(stream->request.method), stream->request.path, num_parts);
|
||||
|
||||
int files_saved = 0;
|
||||
for (int i = 0; i < num_parts; i++) {
|
||||
if (parts[i].filename && parts[i].filename[0] && parts[i].data_len > 0) {
|
||||
char upload_dir[4096];
|
||||
int r = snprintf(upload_dir, sizeof(upload_dir), "%s/uploads", h2->root_dir);
|
||||
if (r > 0 && r < (int)sizeof(upload_dir)) {
|
||||
cocoon_mkdir(upload_dir);
|
||||
char file_path[4096];
|
||||
r = snprintf(file_path, sizeof(file_path), "%s/%s", upload_dir, parts[i].filename);
|
||||
if (r > 0 && r < (int)sizeof(file_path)) {
|
||||
FILE *fp = fopen(file_path, "wb");
|
||||
if (fp) {
|
||||
fwrite(parts[i].data, 1, parts[i].data_len, fp);
|
||||
fclose(fp);
|
||||
files_saved++;
|
||||
if (files_saved > 1) {
|
||||
n += snprintf(response + n, sizeof(response) - n, ",");
|
||||
}
|
||||
n += snprintf(response + n, sizeof(response) - n,
|
||||
"{\"field\":\"%s\",\"filename\":\"%s\",\"size\":%zu,\"path\":\"%s\"}",
|
||||
parts[i].name ? parts[i].name : "",
|
||||
parts[i].filename,
|
||||
parts[i].data_len,
|
||||
file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n += snprintf(response + n, sizeof(response) - n, "]}");
|
||||
multipart_parts_free(parts, num_parts);
|
||||
} else {
|
||||
/* JSON / form-urlencoded 回显 */
|
||||
n += snprintf(response + n, sizeof(response) - n,
|
||||
"{\"method\":\"%s\",\"path\":\"%s\",\"content_type\":\"%s\",\"body_length\":%zu",
|
||||
http_method_str(stream->request.method), stream->request.path,
|
||||
stream->request.content_type, stream->request.body_len);
|
||||
|
||||
if (stream->request.body_len > 0) {
|
||||
if (strstr(stream->request.content_type, "application/json") != NULL) {
|
||||
n += snprintf(response + n, sizeof(response) - n, ",\"body\": ");
|
||||
size_t body_copy = stream->request.body_len;
|
||||
if (body_copy > sizeof(response) - n - 64) {
|
||||
body_copy = sizeof(response) - n - 64;
|
||||
}
|
||||
memcpy(response + n, stream->request.body, body_copy);
|
||||
n += (int)body_copy;
|
||||
n += snprintf(response + n, sizeof(response) - n, "}");
|
||||
} else if (strstr(stream->request.content_type, "x-www-form-urlencoded") != NULL) {
|
||||
n += snprintf(response + n, sizeof(response) - n, ",\"body\":\"");
|
||||
size_t body_copy = stream->request.body_len;
|
||||
if (body_copy > sizeof(response) - n - 64) {
|
||||
body_copy = sizeof(response) - n - 64;
|
||||
}
|
||||
memcpy(response + n, stream->request.body, body_copy);
|
||||
n += (int)body_copy;
|
||||
n += snprintf(response + n, sizeof(response) - n, "\"}");
|
||||
} else {
|
||||
n += snprintf(response + n, sizeof(response) - n, "}");
|
||||
}
|
||||
} else {
|
||||
n += snprintf(response + n, sizeof(response) - n, "}");
|
||||
}
|
||||
}
|
||||
|
||||
/* 存储响应到流 */
|
||||
stream->response_body = (char *)malloc(n);
|
||||
if (!stream->response_body) {
|
||||
nghttp2_nv hdrs[] = {
|
||||
{(uint8_t *)":status", (uint8_t *)"500", 7, 3, 0}};
|
||||
nghttp2_submit_response(h2->session, stream->stream_id, hdrs, 1, NULL);
|
||||
return;
|
||||
}
|
||||
memcpy(stream->response_body, response, n);
|
||||
stream->response_len = (size_t)n;
|
||||
stream->response_sent = 0;
|
||||
|
||||
/* 构建响应头 */
|
||||
nghttp2_nv hdrs[8];
|
||||
int num_hdrs = 0;
|
||||
|
||||
hdrs[num_hdrs++] = (nghttp2_nv){
|
||||
(uint8_t *)":status", (uint8_t *)"200", 7, 3, 0};
|
||||
|
||||
hdrs[num_hdrs++] = (nghttp2_nv){
|
||||
(uint8_t *)"content-type", (uint8_t *)"application/json", 12, 16, 0};
|
||||
|
||||
char content_length_str[32];
|
||||
snprintf(content_length_str, sizeof(content_length_str), "%d", n);
|
||||
hdrs[num_hdrs++] = (nghttp2_nv){
|
||||
(uint8_t *)"content-length", (uint8_t *)content_length_str, 14, strlen(content_length_str), 0};
|
||||
|
||||
hdrs[num_hdrs++] = (nghttp2_nv){
|
||||
(uint8_t *)"server", (uint8_t *)"Cocoon/1.0", 6, 10, 0};
|
||||
|
||||
nghttp2_data_provider provider;
|
||||
provider.source.ptr = stream;
|
||||
provider.read_callback = http2_data_source_read_callback;
|
||||
nghttp2_submit_response(h2->session, stream->stream_id, hdrs, num_hdrs, &provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* http2_serve_static - 处理 HTTP/2 静态文件请求
|
||||
*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user