feat(http): 实现 HTTP/1.1 请求解析与响应构建
- http_parse_request: 解析请求行 + 头部提取 - http_format_response_header: 格式化响应头(支持 Range) - http_mime_type: 25+ 种常见 MIME 类型自动识别
This commit is contained in:
parent
84b415470b
commit
0ab3291035
69
cocoon.h
Normal file
69
cocoon.h
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* cocoon.h - Cocoon 公共头文件
|
||||
*
|
||||
* 定义错误码、配置结构体、函数声明。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#ifndef COCOON_H
|
||||
#define COCOON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* === 错误码 === */
|
||||
#define COCOON_OK 0 /**< 成功 */
|
||||
#define COCOON_ERROR -1 /**< 通用错误 */
|
||||
#define COCOON_NOMEM -2 /**< 内存不足 */
|
||||
#define COCOON_NOTFOUND -3 /**< 文件未找到 */
|
||||
#define COCOONForbidden -4 /**< 禁止访问 */
|
||||
#define COCOON_BADREQUEST -5 /**< 请求格式错误 */
|
||||
|
||||
/* === 服务器配置 === */
|
||||
/**
|
||||
* cocoon_config - 服务器配置结构体
|
||||
*
|
||||
* 命令行参数解析后的配置。
|
||||
*/
|
||||
typedef struct cocoon_config {
|
||||
const char *root_dir; /**< 静态资源根目录 */
|
||||
uint16_t port; /**< 监听端口 */
|
||||
bool threaded; /**< 是否启用多线程调度 */
|
||||
uint32_t num_workers; /**< 工作线程数(0 = 自动检测) */
|
||||
bool verbose; /**< 详细日志输出 */
|
||||
} cocoon_config_t;
|
||||
|
||||
/* === 服务器生命周期 API === */
|
||||
/**
|
||||
* cocoon_server_create - 创建服务器实例
|
||||
*
|
||||
* @param config 配置指针
|
||||
* @return 服务器句柄,失败返回 NULL
|
||||
*/
|
||||
void *cocoon_server_create(const cocoon_config_t *config);
|
||||
|
||||
/**
|
||||
* cocoon_server_run - 启动服务器(阻塞)
|
||||
*
|
||||
* @param server 服务器句柄
|
||||
* @return COCOON_OK 成功,负值错误码
|
||||
*/
|
||||
int cocoon_server_run(void *server);
|
||||
|
||||
/**
|
||||
* cocoon_server_stop - 优雅关闭服务器
|
||||
*
|
||||
* @param server 服务器句柄
|
||||
*/
|
||||
void cocoon_server_stop(void *server);
|
||||
|
||||
/**
|
||||
* cocoon_server_destroy - 销毁服务器实例
|
||||
*
|
||||
* @param server 服务器句柄
|
||||
*/
|
||||
void cocoon_server_destroy(void *server);
|
||||
|
||||
#endif /* COCOON_H */
|
||||
287
http.c
Normal file
287
http.c
Normal file
@ -0,0 +1,287 @@
|
||||
/**
|
||||
* http.c - HTTP 解析与响应构建
|
||||
*
|
||||
* 轻量级 HTTP/1.1 解析器,支持请求解析、响应头格式化、MIME 类型识别。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#include "http.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/**
|
||||
* http_method_str - HTTP 方法枚举转字符串
|
||||
*
|
||||
* @param method HTTP 方法枚举值
|
||||
* @return 方法名称字符串,未知方法返回 "UNKNOWN"
|
||||
*/
|
||||
const char *http_method_str(http_method_t method) {
|
||||
switch (method) {
|
||||
case HTTP_GET: return "GET";
|
||||
case HTTP_HEAD: return "HEAD";
|
||||
case HTTP_POST: return "POST";
|
||||
case HTTP_PUT: return "PUT";
|
||||
case HTTP_DELETE: return "DELETE";
|
||||
case HTTP_OPTIONS: return "OPTIONS";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_method - 从缓冲区开头解析 HTTP 方法
|
||||
*
|
||||
* @param buf 输入缓冲区
|
||||
* @return 解析出的 HTTP 方法枚举
|
||||
*/
|
||||
static http_method_t parse_method(const char *buf) {
|
||||
if (strncmp(buf, "GET ", 4) == 0) return HTTP_GET;
|
||||
if (strncmp(buf, "HEAD ", 5) == 0) return HTTP_HEAD;
|
||||
if (strncmp(buf, "POST ", 5) == 0) return HTTP_POST;
|
||||
if (strncmp(buf, "PUT ", 4) == 0) return HTTP_PUT;
|
||||
if (strncmp(buf, "DELETE ", 7) == 0) return HTTP_DELETE;
|
||||
if (strncmp(buf, "OPTIONS ", 8) == 0) return HTTP_OPTIONS;
|
||||
return HTTP_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_headers - 解析请求头键值对
|
||||
*
|
||||
* 逐行解析,填充 headers 数组,同时提取 content-length、connection 等常用头。
|
||||
*
|
||||
* @param p 当前解析位置指针(会被推进)
|
||||
* @param end 缓冲区结尾
|
||||
* @param req 请求结构体
|
||||
*/
|
||||
static void parse_headers(const char **p, const char *end, http_request_t *req) {
|
||||
req->num_headers = 0;
|
||||
req->content_length = -1;
|
||||
req->keep_alive = false;
|
||||
req->has_range = false;
|
||||
req->range_start = 0;
|
||||
req->range_end = -1;
|
||||
|
||||
while (*p < end && req->num_headers < HTTP_MAX_HEADERS) {
|
||||
const char *line_start = *p;
|
||||
|
||||
/* 查找行尾 */
|
||||
const char *line_end = memchr(line_start, '\n', end - line_start);
|
||||
if (!line_end) break;
|
||||
|
||||
/* 空行表示头部结束 */
|
||||
if (line_end == line_start || (line_end == line_start + 1 && line_start[0] == '\r')) {
|
||||
*p = line_end + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 跳过末尾 \r */
|
||||
const char *line_tail = line_end;
|
||||
if (line_tail > line_start && line_tail[-1] == '\r') line_tail--;
|
||||
|
||||
/* 查找冒号分隔符 */
|
||||
const char *colon = memchr(line_start, ':', line_tail - line_start);
|
||||
if (colon) {
|
||||
int name_len = colon - line_start;
|
||||
if (name_len > 0 && name_len < HTTP_HEADER_NAME_MAX) {
|
||||
/* 复制名称(转小写便于匹配) */
|
||||
for (int i = 0; i < name_len && i < HTTP_HEADER_NAME_MAX - 1; i++) {
|
||||
req->headers[req->num_headers].name[i] = (char)tolower((unsigned char)line_start[i]);
|
||||
}
|
||||
req->headers[req->num_headers].name[name_len > HTTP_HEADER_NAME_MAX - 1 ? HTTP_HEADER_NAME_MAX - 1 : name_len] = '\0';
|
||||
|
||||
/* 复制值(跳过冒号后空格) */
|
||||
const char *val = colon + 1;
|
||||
while (val < line_tail && (*val == ' ' || *val == '\t')) val++;
|
||||
int val_len = line_tail - val;
|
||||
if (val_len > 0 && val_len < HTTP_HEADER_VALUE_MAX) {
|
||||
int copy_len = val_len > HTTP_HEADER_VALUE_MAX - 1 ? HTTP_HEADER_VALUE_MAX - 1 : val_len;
|
||||
memcpy(req->headers[req->num_headers].value, val, copy_len);
|
||||
req->headers[req->num_headers].value[copy_len] = '\0';
|
||||
}
|
||||
|
||||
/* 提取常用头 */
|
||||
if (strcmp(req->headers[req->num_headers].name, "content-length") == 0) {
|
||||
req->content_length = atoll(req->headers[req->num_headers].value);
|
||||
} else if (strcmp(req->headers[req->num_headers].name, "connection") == 0) {
|
||||
req->keep_alive = (strcasecmp(req->headers[req->num_headers].value, "keep-alive") == 0);
|
||||
} else if (strcmp(req->headers[req->num_headers].name, "range") == 0) {
|
||||
/* 解析 Range: bytes=start-end */
|
||||
const char *range_val = req->headers[req->num_headers].value;
|
||||
if (strncasecmp(range_val, "bytes=", 6) == 0) {
|
||||
req->has_range = true;
|
||||
req->range_start = atoll(range_val + 6);
|
||||
const char *dash = strchr(range_val + 6, '-');
|
||||
if (dash) {
|
||||
req->range_end = atoll(dash + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req->num_headers++;
|
||||
}
|
||||
}
|
||||
|
||||
*p = line_end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* http_parse_request - 解析 HTTP 请求
|
||||
*
|
||||
* 从缓冲区解析 HTTP/1.x 请求行和头部,填充 http_request_t 结构体。
|
||||
* 不解析请求体。
|
||||
*
|
||||
* @param buf 输入缓冲区
|
||||
* @param len 缓冲区长度
|
||||
* @param req 输出请求结构体
|
||||
* @return 成功解析的字节数,-1 表示数据不完整,-2 表示格式错误
|
||||
*/
|
||||
int http_parse_request(const char *buf, size_t len, http_request_t *req) {
|
||||
if (!buf || !req || len == 0) return -2;
|
||||
|
||||
memset(req, 0, sizeof(*req));
|
||||
|
||||
/* 查找请求行结尾(\r\n) */
|
||||
const char *line_end = memchr(buf, '\n', len);
|
||||
if (!line_end) return -1; /* 数据不完整 */
|
||||
|
||||
int line_len = line_end - buf;
|
||||
if (line_len > 0 && buf[line_len - 1] == '\r') line_len--;
|
||||
if (line_len < 10) return -2; /* 请求行太短 */
|
||||
|
||||
/* 解析方法 */
|
||||
req->method = parse_method(buf);
|
||||
if (req->method == HTTP_UNKNOWN) return -2;
|
||||
|
||||
/* 提取路径 */
|
||||
const char *path_start = strchr(buf, ' ');
|
||||
if (!path_start) return -2;
|
||||
path_start++;
|
||||
|
||||
const char *path_end = strchr(path_start, ' ');
|
||||
if (!path_end) return -2;
|
||||
|
||||
int path_len = path_end - path_start;
|
||||
if (path_len <= 0 || path_len >= HTTP_MAX_PATH) return -2;
|
||||
memcpy(req->path, path_start, path_len);
|
||||
req->path[path_len] = '\0';
|
||||
|
||||
/* 提取 HTTP 版本 */
|
||||
const char *ver_start = path_end + 1;
|
||||
int ver_len = line_len - (ver_start - buf);
|
||||
if (ver_len > 0 && ver_len < (int)sizeof(req->version)) {
|
||||
memcpy(req->version, ver_start, ver_len);
|
||||
req->version[ver_len] = '\0';
|
||||
}
|
||||
|
||||
/* 解析头部 */
|
||||
const char *p = line_end + 1;
|
||||
const char *end = buf + len;
|
||||
parse_headers(&p, end, req);
|
||||
|
||||
/* HTTP/1.1 默认 keep-alive */
|
||||
if (strstr(req->version, "1.1") != NULL) {
|
||||
req->keep_alive = true;
|
||||
}
|
||||
|
||||
return p - buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* http_format_response_header - 格式化 HTTP 响应头
|
||||
*
|
||||
* 将响应信息格式化为标准 HTTP/1.1 响应头字符串。
|
||||
*
|
||||
* @param buf 输出缓冲区
|
||||
* @param buf_size 缓冲区大小
|
||||
* @param resp 响应信息结构体
|
||||
* @return 实际写入的字节数,负值表示缓冲区不足
|
||||
*/
|
||||
int http_format_response_header(char *buf, size_t buf_size, const http_response_t *resp) {
|
||||
if (!buf || !resp || buf_size == 0) return -1;
|
||||
|
||||
int n = 0;
|
||||
|
||||
/* 状态行 */
|
||||
if (resp->has_range) {
|
||||
n += snprintf(buf + n, buf_size - n,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %ld\r\n"
|
||||
"Accept-Ranges: bytes\r\n"
|
||||
"Content-Range: bytes %ld-%ld/%ld\r\n"
|
||||
"Connection: %s\r\n"
|
||||
"Server: Cocoon/1.0\r\n"
|
||||
"\r\n",
|
||||
resp->status_code, resp->status_text,
|
||||
resp->content_type ? resp->content_type : "application/octet-stream",
|
||||
(long)resp->content_length,
|
||||
(long)resp->range_start, (long)resp->range_end, (long)resp->total_length,
|
||||
resp->keep_alive ? "keep-alive" : "close");
|
||||
} else {
|
||||
n += snprintf(buf + n, buf_size - n,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %ld\r\n"
|
||||
"Accept-Ranges: bytes\r\n"
|
||||
"Connection: %s\r\n"
|
||||
"Server: Cocoon/1.0\r\n"
|
||||
"\r\n",
|
||||
resp->status_code, resp->status_text,
|
||||
resp->content_type ? resp->content_type : "application/octet-stream",
|
||||
(long)resp->content_length,
|
||||
resp->keep_alive ? "keep-alive" : "close");
|
||||
}
|
||||
|
||||
if ((size_t)n >= buf_size) return -1;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* http_mime_type - 根据文件扩展名推断 MIME 类型
|
||||
*
|
||||
* 支持常见静态资源类型的自动识别。
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return MIME 类型字符串
|
||||
*/
|
||||
const char *http_mime_type(const char *path) {
|
||||
const char *ext = strrchr(path, '.');
|
||||
if (!ext) return "application/octet-stream";
|
||||
|
||||
/* 转小写比较 */
|
||||
char lower_ext[16];
|
||||
int i = 0;
|
||||
for (const char *p = ext + 1; *p && i < 15; p++, i++) {
|
||||
lower_ext[i] = (char)tolower((unsigned char)*p);
|
||||
}
|
||||
lower_ext[i] = '\0';
|
||||
|
||||
if (strcmp(lower_ext, "html") == 0 || strcmp(lower_ext, "htm") == 0) return "text/html; charset=utf-8";
|
||||
if (strcmp(lower_ext, "css") == 0) return "text/css; charset=utf-8";
|
||||
if (strcmp(lower_ext, "js") == 0) return "application/javascript";
|
||||
if (strcmp(lower_ext, "json") == 0) return "application/json";
|
||||
if (strcmp(lower_ext, "png") == 0) return "image/png";
|
||||
if (strcmp(lower_ext, "jpg") == 0 || strcmp(lower_ext, "jpeg") == 0) return "image/jpeg";
|
||||
if (strcmp(lower_ext, "gif") == 0) return "image/gif";
|
||||
if (strcmp(lower_ext, "svg") == 0) return "image/svg+xml";
|
||||
if (strcmp(lower_ext, "ico") == 0) return "image/x-icon";
|
||||
if (strcmp(lower_ext, "woff2") == 0) return "font/woff2";
|
||||
if (strcmp(lower_ext, "woff") == 0) return "font/woff";
|
||||
if (strcmp(lower_ext, "ttf") == 0) return "font/ttf";
|
||||
if (strcmp(lower_ext, "txt") == 0) return "text/plain; charset=utf-8";
|
||||
if (strcmp(lower_ext, "md") == 0) return "text/markdown; charset=utf-8";
|
||||
if (strcmp(lower_ext, "xml") == 0) return "application/xml";
|
||||
if (strcmp(lower_ext, "pdf") == 0) return "application/pdf";
|
||||
if (strcmp(lower_ext, "zip") == 0) return "application/zip";
|
||||
if (strcmp(lower_ext, "gz") == 0) return "application/gzip";
|
||||
if (strcmp(lower_ext, "mp4") == 0) return "video/mp4";
|
||||
if (strcmp(lower_ext, "webm") == 0) return "video/webm";
|
||||
if (strcmp(lower_ext, "mp3") == 0) return "audio/mpeg";
|
||||
if (strcmp(lower_ext, "ogg") == 0) return "audio/ogg";
|
||||
if (strcmp(lower_ext, "wasm") == 0) return "application/wasm";
|
||||
if (strcmp(lower_ext, "webmanifest") == 0) return "application/manifest+json";
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
102
http.h
Normal file
102
http.h
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* http.h - HTTP 解析模块接口
|
||||
*
|
||||
* 提供 HTTP 请求解析和响应构建的接口。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#ifndef COCOON_HTTP_H
|
||||
#define COCOON_HTTP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* === HTTP 方法 === */
|
||||
typedef enum {
|
||||
HTTP_GET,
|
||||
HTTP_HEAD,
|
||||
HTTP_POST,
|
||||
HTTP_PUT,
|
||||
HTTP_DELETE,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_UNKNOWN
|
||||
} http_method_t;
|
||||
|
||||
/* === HTTP 请求 === */
|
||||
#define HTTP_MAX_PATH 4096
|
||||
#define HTTP_MAX_HEADERS 32
|
||||
#define HTTP_HEADER_NAME_MAX 64
|
||||
#define HTTP_HEADER_VALUE_MAX 1024
|
||||
|
||||
typedef struct {
|
||||
http_method_t method;
|
||||
char path[HTTP_MAX_PATH];
|
||||
char version[16];
|
||||
|
||||
/* 请求头 */
|
||||
struct {
|
||||
char name[HTTP_HEADER_NAME_MAX];
|
||||
char value[HTTP_HEADER_VALUE_MAX];
|
||||
} headers[HTTP_MAX_HEADERS];
|
||||
int num_headers;
|
||||
|
||||
/* 常用头快速访问 */
|
||||
int64_t content_length;
|
||||
bool keep_alive;
|
||||
bool has_range;
|
||||
int64_t range_start;
|
||||
int64_t range_end;
|
||||
} http_request_t;
|
||||
|
||||
/* === HTTP 响应 === */
|
||||
typedef struct {
|
||||
int status_code;
|
||||
const char *status_text;
|
||||
const char *content_type;
|
||||
int64_t content_length;
|
||||
bool keep_alive;
|
||||
bool has_range;
|
||||
int64_t range_start;
|
||||
int64_t range_end;
|
||||
int64_t total_length;
|
||||
} http_response_t;
|
||||
|
||||
/* === API === */
|
||||
/**
|
||||
* http_parse_request - 从缓冲区解析 HTTP 请求
|
||||
*
|
||||
* @param buf 输入缓冲区
|
||||
* @param len 缓冲区长度
|
||||
* @param req 输出请求结构体
|
||||
* @return 成功解析的字节数,< 0 表示错误(-1 = 不完整,-2 = 格式错误)
|
||||
*/
|
||||
int http_parse_request(const char *buf, size_t len, http_request_t *req);
|
||||
|
||||
/**
|
||||
* http_format_response_header - 格式化响应头到缓冲区
|
||||
*
|
||||
* @param buf 输出缓冲区
|
||||
* @param buf_size 缓冲区大小
|
||||
* @param resp 响应信息
|
||||
* @return 写入的字节数,< 0 表示缓冲区不足
|
||||
*/
|
||||
int http_format_response_header(char *buf, size_t buf_size, const http_response_t *resp);
|
||||
|
||||
/**
|
||||
* http_method_str - HTTP 方法转字符串
|
||||
*
|
||||
* @param method HTTP 方法
|
||||
* @return 方法字符串(如 "GET")
|
||||
*/
|
||||
const char *http_method_str(http_method_t method);
|
||||
|
||||
/**
|
||||
* http_mime_type - 根据文件扩展名获取 MIME 类型
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return MIME 类型字符串
|
||||
*/
|
||||
const char *http_mime_type(const char *path);
|
||||
|
||||
#endif /* COCOON_HTTP_H */
|
||||
Loading…
x
Reference in New Issue
Block a user