fix(build): 修复 Range 解析嵌套问题 + 补充 errno 头文件 + 链接 zlib

- http.c: 修复 Range 请求解析被错误嵌套在 accept-encoding 分支的 bug
- static.c: 补充缺失的 <errno.h> 和 <zlib.h> 头文件,修复编译失败
- Makefile: 添加 -lz 链接参数,支持 gzip 压缩功能
This commit is contained in:
xfy911 2026-06-03 18:18:33 +08:00
parent 0dc2bb5b4e
commit 5adf5e0581
4 changed files with 136 additions and 25 deletions

View File

@ -7,7 +7,7 @@ COCO_LIB ?= $(COCO_DIR)/build
CC = gcc
CFLAGS = -Wall -Wextra -O2 -std=c11 -I$(COCO_INCLUDE)
LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring
LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring -lz
# 调试模式
DEBUG ?= 0

5
http.c
View File

@ -130,7 +130,7 @@ static void parse_headers(const char **p, const char *end, http_request_t *req)
const char *val = req->headers[req->num_headers].value;
if (strstr(val, "gzip") != NULL) req->accept_gzip = true;
if (strstr(val, "deflate") != NULL) req->accept_deflate = true;
/* 解析 Range: bytes=start-end */
} else if (strcmp(req->headers[req->num_headers].name, "range") == 0) {
const char *range_val = req->headers[req->num_headers].value;
if (strncasecmp(range_val, "bytes=", 6) == 0) {
req->has_range = true;
@ -257,6 +257,9 @@ int http_format_response_header(char *buf, size_t buf_size, const http_response_
if (resp->last_modified && resp->last_modified[0]) {
n += snprintf(buf + n, buf_size - n, "Last-Modified: %s\r\n", resp->last_modified);
}
if (resp->content_encoding && resp->content_encoding[0]) {
n += snprintf(buf + n, buf_size - n, "Content-Encoding: %s\r\n", resp->content_encoding);
}
/* 连接头 + 空行 */
n += snprintf(buf + n, buf_size - n,

3
http.h
View File

@ -76,6 +76,9 @@ typedef struct {
/* 缓存 */
const char *etag;
const char *last_modified;
/* 压缩 */
const char *content_encoding;
} http_response_t;
/* === API === */

151
static.c
View File

@ -21,6 +21,68 @@
#include <time.h>
#include <dirent.h>
#include <errno.h>
#include <zlib.h>
/**
* is_compressible_mime - MIME
*
*
*
*
* @param mime_type MIME
* @return true
*/
static bool is_compressible_mime(const char *mime_type) {
if (!mime_type) return false;
return (
strstr(mime_type, "text/") != NULL ||
strstr(mime_type, "application/javascript") != NULL ||
strstr(mime_type, "application/json") != NULL ||
strstr(mime_type, "application/xml") != NULL ||
strstr(mime_type, "application/manifest") != NULL ||
strstr(mime_type, "image/svg") != NULL
);
}
/**
* gzip_compress - 使 zlib gzip
*
* 使 deflateInit2 windowBits = 15 + 16 gzip
*
* @param src
* @param src_len
* @param dst src_len
* @param dst_cap
* @return 0 -1
*/
static ssize_t gzip_compress(const char *src, size_t src_len,
char *dst, size_t dst_cap) {
z_stream strm = {0};
/* 15 + 16 = gzip 格式 */
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
return -1;
}
strm.avail_in = (uInt)src_len;
strm.next_in = (Bytef *)src;
strm.avail_out = (uInt)dst_cap;
strm.next_out = (Bytef *)dst;
if (deflate(&strm, Z_FINISH) != Z_STREAM_END) {
deflateEnd(&strm);
return -1;
}
size_t compressed_len = dst_cap - strm.avail_out;
deflateEnd(&strm);
/* 如果压缩后更大或差不多,就不压缩了 */
if (compressed_len >= src_len * 0.95) {
return 0;
}
return (ssize_t)compressed_len;
}
/**
* format_http_time - HTTP
@ -278,25 +340,57 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir) {
}
}
/* 计算发送范围 */
/* 判断是否 gzip 压缩 */
int64_t file_size = st.st_size;
bool use_gzip = false;
char *gzip_buf = NULL;
ssize_t gzip_len = 0;
if (req->accept_gzip && !req->has_range && req->method != HTTP_HEAD) {
const char *mime = http_mime_type(real_path);
if (is_compressible_mime(mime) && file_size > 256) {
/* 读取文件内容到内存 */
char *file_buf = (char *)malloc((size_t)file_size);
if (file_buf) {
ssize_t read_total = 0;
while (read_total < file_size) {
ssize_t n = read(file_fd, file_buf + read_total, (size_t)(file_size - read_total));
if (n <= 0) break;
read_total += n;
}
if (read_total == file_size) {
gzip_buf = (char *)malloc((size_t)file_size);
if (gzip_buf) {
gzip_len = gzip_compress(file_buf, (size_t)file_size, gzip_buf, (size_t)file_size);
if (gzip_len > 0) {
use_gzip = true;
}
}
}
free(file_buf);
}
}
}
/* 计算发送范围 */
int64_t send_start = 0;
int64_t send_end = file_size - 1;
int status_code = 200;
if (req->has_range) {
if (!use_gzip && req->has_range) {
send_start = req->range_start;
if (req->range_end >= 0 && req->range_end < file_size) {
send_end = req->range_end;
}
if (send_start >= file_size || send_start > send_end) {
close(file_fd);
free(gzip_buf);
return static_send_error(fd, 416, req->keep_alive);
}
status_code = 206;
}
int64_t send_length = send_end - send_start + 1;
int64_t send_length = use_gzip ? gzip_len : (send_end - send_start + 1);
/* 构建响应头 */
http_response_t resp = {
@ -305,24 +399,27 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir) {
.content_type = http_mime_type(real_path),
.content_length = send_length,
.keep_alive = req->keep_alive,
.has_range = req->has_range,
.has_range = !use_gzip && req->has_range,
.range_start = send_start,
.range_end = send_end,
.total_length = file_size,
.etag = etag,
.last_modified = last_modified
.last_modified = last_modified,
.content_encoding = use_gzip ? "gzip" : NULL
};
char header_buf[1024];
int header_len = http_format_response_header(header_buf, sizeof(header_buf), &resp);
if (header_len < 0) {
close(file_fd);
free(gzip_buf);
return static_send_error(fd, 500, req->keep_alive);
}
/* 发送响应头 */
if (send_all(fd, header_buf, (size_t)header_len) != 0) {
close(file_fd);
free(gzip_buf);
return COCOON_ERROR;
}
@ -330,29 +427,37 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir) {
if (req->method == HTTP_HEAD) {
/* HEAD 请求不发送 body */
close(file_fd);
free(gzip_buf);
return COCOON_OK;
}
/* 定位到起始位置 */
if (send_start > 0) {
lseek(file_fd, send_start, SEEK_SET);
}
/* 使用 sendfile 零拷贝发送 */
off_t offset = send_start;
ssize_t remaining = send_length;
while (remaining > 0) {
ssize_t n = sendfile(fd, file_fd, &offset, (size_t)remaining);
if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
/* sendfile 失败,回退到 read/write */
break;
if (use_gzip) {
/* 发送 gzip 压缩后的数据 */
send_all(fd, gzip_buf, (size_t)gzip_len);
free(gzip_buf);
close(file_fd);
} else {
/* 定位到起始位置 */
if (send_start > 0) {
lseek(file_fd, send_start, SEEK_SET);
}
if (n == 0) break;
remaining -= n;
}
close(file_fd);
/* 使用 sendfile 零拷贝发送 */
off_t offset = send_start;
ssize_t remaining = send_length;
while (remaining > 0) {
ssize_t n = sendfile(fd, file_fd, &offset, (size_t)remaining);
if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
/* sendfile 失败,回退到 read/write */
break;
}
if (n == 0) break;
remaining -= n;
}
close(file_fd);
}
return COCOON_OK;
}