diff --git a/Makefile b/Makefile index 4f84806..a86719a 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ COCO_INCLUDE ?= $(COCO_DIR)/include COCO_LIB ?= $(COCO_DIR)/build CC = gcc -CFLAGS = -Wall -Wextra -O2 -std=c11 -I$(COCO_INCLUDE) +CFLAGS = -Wall -Wextra -O2 -std=c11 -D_GNU_SOURCE -I$(COCO_INCLUDE) LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring -lz # 调试模式 @@ -20,7 +20,7 @@ PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin # 源文件 -SRCS = main.c server.c http.c static.c log.c config.c +SRCS = main.c server.c http.c static.c log.c config.c multipart.c OBJS = $(SRCS:.c=.o) TARGET = cocoon @@ -78,17 +78,20 @@ unit-test: $(UNIT_TEST_BINS) fi # 单元测试编译规则 +$(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) + $(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) -lm + $(UNIT_TEST_DIR)/test_http: $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC) - $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC) -lm + $(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC) -lm $(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c $(UNITY_SRC) - $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c $(UNITY_SRC) -lm -lz + $(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c $(UNITY_SRC) -lm -lz $(UNIT_TEST_DIR)/test_log: $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) - $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) -lm + $(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) -lm $(UNIT_TEST_DIR)/test_config: $(UNIT_TEST_DIR)/test_config.c config.c log.c $(UNITY_SRC) - $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c $(UNITY_SRC) -lm + $(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c $(UNITY_SRC) -lm # 编译规则 %.o: %.c diff --git a/config.c b/config.c index 170e4e7..af8c131 100644 --- a/config.c +++ b/config.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE /** * config.c - JSON 配置文件解析实现 diff --git a/http.c b/http.c index 226b75b..9fad846 100644 --- a/http.c +++ b/http.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE /** * http.c - HTTP 解析与响应构建 diff --git a/server.c b/server.c index 38b1bd4..af3b924 100644 --- a/server.c +++ b/server.c @@ -1,7 +1,4 @@ -#define _GNU_SOURCE - /** - * server.c - 服务器核心实现 * * 基于 coco 协程库实现高并发静态资源服务器。 * 每个客户端连接由一个独立的协程处理,主线程负责 accept。 @@ -23,6 +20,7 @@ #include "static.h" #include "cocoon.h" #include "log.h" +#include "multipart.h" #include #include #include @@ -191,17 +189,91 @@ static int conn_read_body(connection_t *conn, http_request_t *req, size_t need) /** * handle_post_request - 处理 POST 请求 * - * 目前仅提供 JSON 回显和表单回显,用于测试和 API 场景。 - * 支持 application/json 和 application/x-www-form-urlencoded。 + * 支持 multipart/form-data 文件上传、JSON 回显和表单回显。 * * @param fd 客户端 socket * @param req HTTP 请求 + * @param root_dir 静态资源根目录(用于保存上传文件) * @return true 保持连接 */ -static bool handle_post_request(int fd, const http_request_t *req) { +static bool handle_post_request(int fd, const http_request_t *req, const char *root_dir) { char response[4096]; int n = 0; + /* multipart/form-data 文件上传 */ + if (strstr(req->content_type, "multipart/form-data") != NULL) { + char boundary[256]; + if (!multipart_extract_boundary(req->content_type, boundary, sizeof(boundary))) { + static_send_error(fd, 400, req->keep_alive); + return req->keep_alive; + } + + multipart_part_t *parts = NULL; + int num_parts = 0; + if (multipart_parse(req->body, req->body_len, boundary, &parts, &num_parts) != 0) { + static_send_error(fd, 400, req->keep_alive); + return req->keep_alive; + } + + /* 构建 JSON 响应 */ + n += snprintf(response + n, sizeof(response) - n, + "{\"method\":\"%s\",\"path\":\"%s\",\"uploaded\":%d,\"files\":[", + http_method_str(req->method), req->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) { + /* 保存文件到 root_dir/uploads/ */ + char upload_dir[4096]; + int r = snprintf(upload_dir, sizeof(upload_dir), "%s/uploads", root_dir); + if (r > 0 && r < (int)sizeof(upload_dir)) { + /* 创建目录(忽略已存在错误) */ + mkdir(upload_dir, 0755); + + 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); + + char header[512]; + int header_len = snprintf(header, sizeof(header), + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Content-Length: %d\r\n" + "Connection: %s\r\n" + "Server: Cocoon/1.0\r\n" + "\r\n", + n, req->keep_alive ? "keep-alive" : "close"); + + send_all(fd, header, (size_t)header_len); + send_all(fd, response, (size_t)n); + + return req->keep_alive; + } + n += snprintf(response + n, sizeof(response) - n, "{\"method\":\"%s\",\"path\":\"%s\",\"content_type\":\"%s\",\"body_length\":%zu", http_method_str(req->method), req->path, req->content_type, req->body_len); @@ -291,7 +363,7 @@ static bool handle_request(connection_t *conn, const char *root_dir) { /* 处理 POST */ if (req.method == HTTP_POST) { - bool keep = handle_post_request(conn->fd, &req); + bool keep = handle_post_request(conn->fd, &req, conn->root_dir); http_request_free(&req); return keep; } diff --git a/static.c b/static.c index c569b4e..870f0b4 100644 --- a/static.c +++ b/static.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE /** * static.c - 静态资源服务实现