HTTP 1.x Server 精简版教程
概述
本教程介绍如何使用 libcc 内置的 HTTP 解析功能,示例代码基于项目中的 tests/test_http.c,此处为精简版实现。
HTTP 协议版本特性
HTTP/1.0 核心特性
- 请求头/响应头:首次引入头信息概念,采用
Key-Value形式组织。 - 无状态连接:默认每次请求后断开连接,需重新建立。
HTTP/1.1 主要改进
- 持久连接:默认保持连接复用,显著降低延迟。
- Host 字段:支持单 IP 多域名(虚拟主机)。
- 缓存优化:引入更精细的缓存控制机制。
- 流水线支持:允许连续发送多个请求无需等待响应。

简易 HTTP 服务实现
核心数据结构
#include <libcc/dirent.h>
#include <libcc/event.h>
/* HTTP 连接上下文结构体 */
typedef struct _http {
uint8_t state; // 当前HTTP解析状态
size_t payload; // POST请求体大小
_cc_io_buffer_t *io; // 网络读写缓冲区
_cc_http_request_header_t *request; // 解析后的HTTP头部
_cc_buf_t buffer; // 存储POST请求体数据
_cc_file_t *file; // 静态资源文件句柄
} _http_t;
全局配置与回调声明
/* 静态资源根目录 */
static const tchar_t *root = _T("/opt/homebrew/var/www/");
/* 事件回调函数声明 */
// 连接建立
static bool_t onAccept(_cc_async_event_t *async, _cc_event_t *e);
// 连接关闭
static bool_t onClose(_cc_async_event_t *async, _cc_event_t *e);
// 数据读取
static bool_t onRead(_cc_async_event_t *async, _cc_event_t *e);
// 数据发送
static bool_t onWrite(_cc_async_event_t *async, _cc_event_t *e);
// 超时处理
static bool_t onTimeout(_cc_async_event_t *async, _cc_event_t *e);
网络事件分发器
/**
* 事件分发函数
* @param async 异步事件管理器
* @param e 当前事件对象
* @param which 触发的事件类型掩码
* @return 是否继续处理事件
*/
static bool_t doEvent(_cc_async_event_t *async, _cc_event_t *e, const uint32_t which) {
/* 新连接建立 */
if (which & _CC_EVENT_ACCEPT_) {
return onAccept(async, e);
}
/* 连接关闭 */
else if (which & _CC_EVENT_CLOSED_) {
return onClose(async, e);
}
/* 可读事件 */
if (which & _CC_EVENT_READABLE_) {
if (!onRead(async, e)) {
return false;
}
}
/* 可写事件 */
if (which & _CC_EVENT_WRITABLE_) {
if (!onWrite(async, e)) {
return false;
}
}
/* 超时事件 */
if (which & _CC_EVENT_TIMEOUT_) {
if (!onTimeout(async, e)) {
return false;
}
}
return true;
}
连接建立处理
/**
* 新连接建立回调
* @param async 异步事件管理器
* @param e 监听事件对象
* @return 是否成功处理
*/
static bool_t onAccept(_cc_async_event_t *async, _cc_event_t *e) {
_cc_socket_t fd;
_cc_event_t *event;
_http_t *http;
struct sockaddr_in remote_addr = {0};
_cc_socklen_t remote_addr_len = sizeof(struct sockaddr_in);
_cc_async_event_t *async2 = _cc_get_async_event();
// 接受新连接
fd = async->accept(async, e, (_cc_sockaddr_t *)&remote_addr, &remote_addr_len);
if (fd == _CC_INVALID_SOCKET_) {
_cc_logger_debug(_T("thread %d accept fail %s."),
_cc_get_thread_id(nullptr),
_cc_last_error(_cc_last_errno()));
return false;
}
// 创建事件对象
event = _cc_event_alloc(async2, _CC_EVENT_TIMEOUT_ | _CC_EVENT_READABLE_);
if (event == nullptr) {
_cc_close_socket(fd);
return false;
}
// 设置为非阻塞模式
_cc_set_socket_nonblock(fd, 1);
// 初始化HTTP上下文
http = (_http_t*)_cc_malloc(sizeof(_http_t));
http->state = _CC_HTTP_STATUS_HEADER_;
http->request = nullptr;
http->payload = 0;
http->io = _cc_alloc_io_buffer(_CC_IO_BUFFER_SIZE_);
_cc_alloc_buf(&http->buffer, _CC_IO_BUFFER_SIZE_);
// 配置事件参数
event->fd = fd;
event->callback = e->callback;
event->timeout = e->timeout;
event->data = (uintptr_t)http;
// 注册到事件循环
if (async2->attach(async2, event) == false) {
_cc_logger_debug(_T("thread %d add socket (%d) event fial."),
_cc_get_thread_id(nullptr), fd);
_cc_free_event(async2, event);
return false;
}
return true;
}
连接关闭处理
/**
* 连接关闭回调
* @param async 异步事件管理器
* @param e 关闭事件对象
* @return 是否成功释放资源
*/
static bool_t onClose(_cc_async_event_t *async, _cc_event_t *e) {
_cc_logger_debug(_T("%d onClose."), e->ident);
if (e->data) {
_http_t *http = (_http_t*)e->data;
// 释放IO缓冲区
if (http->io) {
_cc_free_io_buffer(http->io);
}
// 释放HTTP请求头
if (http->request) {
_cc_http_free_request_header(&http->request);
}
// 释放数据缓冲区
if (http->buffer.bytes) {
_cc_free_buf(&http->buffer);
}
// 关闭文件句柄
if (http->file) {
_cc_fclose(http->file);
}
// 清理上下文
e->data = 0;
_cc_free(http);
return true;
}
return false;
}
HTTP 响应处理函数
/**
* 400 错误请求响应
* @param e 事件对象
* @param io IO缓冲区
*/
_CC_API_PRIVATE(void) bad_request(_cc_event_t *e, _cc_io_buffer_t *io) {
_cc_string_t body = _cc_string("<HTML><HEAD><TITLE>BAD REQUEST</TITLE></HEAD><BODY><P>Your browser sent a bad request, such as a POST without a Content-Length.</P></BODY></HTML>");
// 构造响应头
io->w.off = snprintf((char*)io->w.bytes, io->w.limit,
"HTTP/1.1 400 BAD REQUEST\r\n"
"Connection: close\r\n"
"Content-type: text/html\r\n"
"Content-Length: %ld\r\n\r\n",
body.length);
// 写入响应体
memcpy(io->w.bytes + io->w.off, body.data, body.length * sizeof(char_t));
io->w.off += (int32_t)body.length * sizeof(char_t);
_cc_io_buffer_flush(e, io);
}
/**
* 404 资源未找到响应
* @param e 事件对象
* @param io IO缓冲区
*/
_CC_API_PRIVATE(void) not_found(_cc_event_t *e, _cc_io_buffer_t *io) {
_cc_string_t body = _cc_string("<HTML><HEAD><TITLE>Not Found</TITLE></HEAD><BODY><p>The server could not find the requested URL.</p></BODY></HTML>");
io->w.off = snprintf((char*)io->w.bytes, io->w.limit,
"HTTP/1.1 404 NOT FOUND\r\n"
"Connection: close\r\n"
"Content-type: text/html\r\n"
"Content-Length: %ld\r\n\r\n",
body.length);
memcpy(io->w.bytes + io->w.off, body.data, body.length * sizeof(char_t));
io->w.off += (int32_t)body.length * sizeof(char_t);
_cc_io_buffer_flush(e, io);
}
/**
* 200 成功响应
* @param e 事件对象
* @param io IO缓冲区
*/
_CC_API_PRIVATE(void) request_ok(_cc_event_t *e, _cc_io_buffer_t *io) {
_cc_string_t body = _cc_string("<HTML><HEAD><TITLE>Welcome to HTTP</TITLE></HEAD><BODY><p>If you see this page, the web server is successfully</p></BODY></HTML>");
io->w.off = snprintf((char*)io->w.bytes, io->w.limit,
"HTTP/1.1 200 OK\r\n"
"Connection: Keep-Alive\r\n"
"Content-type: text/html\r\n"
"Content-Length: %ld\r\n\r\n",
body.length);
memcpy(io->w.bytes + io->w.off, body.data, body.length * sizeof(char_t));
io->w.off += (int32_t)body.length * sizeof(char_t);
_cc_io_buffer_flush(e, io);
}
/**
* 文件响应处理
* @param e 事件对象
* @param io IO缓冲区
* @param www_file 请求的文件路径
*/
_CC_API_PRIVATE(void) request_file(_cc_event_t *e, _cc_io_buffer_t *io, tchar_t *www_file) {
_http_t *http = (_http_t*)e->data;
http->file = _cc_fopen(www_file, "rb");
if (http->file) {
uint64_t size = _cc_file_size(http->file);
io->w.off = snprintf((char*)io->w.bytes, io->w.limit,
"HTTP/1.1 200 OK\r\n"
"Connection: Keep-Alive\r\n"
"Content-type: text/html\r\n"
"Content-Length: %lld\r\n\r\n",
size);
_CC_SET_BIT(_CC_EVENT_WRITABLE_, e->flags);
} else {
not_found(e, io);
}
}
请求处理逻辑
/**
* 从HTTP头中获取Content-Length值
* @param headers HTTP头红黑树
* @return 内容长度(字节)
*/
_CC_API_PRIVATE(int64_t) _get_content_length(_cc_rbtree_t *headers) {
const _cc_http_header_t *data = _cc_http_header_find(headers, _T("Content-Length"));
return (data ? _ttoi(data->value) : 0);
}
/**
* 数据读取回调
* @param async 异步事件管理器
* @param e 事件对象
* @return 是否继续处理
*/
static bool_t onRead(_cc_async_event_t *async, _cc_event_t *e) {
_http_t *http = (_http_t*)e->data;
_cc_io_buffer_t *io = http->io;
// 读取网络数据
int32_t off = _cc_io_buffer_read(e, io);
if (off < 0) {
return false; // 读取失败
} else if (off == 0) {
return true; // 无数据
}
// 已建立连接但未完成请求
if (http->state == _CC_HTTP_STATUS_ESTABLISHED_) {
return false;
}
// 解析HTTP头部
else if (http->state == _CC_HTTP_STATUS_HEADER_) {
http->state = _cc_http_header_parser(
(_cc_http_header_fn_t)_cc_http_alloc_request_header,
(pvoid_t *)&http->request,
io->r.bytes,
&io->r.off
);
// 头部解析未完成
if (http->state == _CC_HTTP_STATUS_HEADER_) {
return true;
}
// 头部解析错误
else if (http->state != _CC_HTTP_STATUS_PAYLOAD_) {
bad_request(e, io);
return false;
}
// 获取POST内容长度
http->payload = _get_content_length(&http->request->headers);
if (http->payload == 0) {
http->state = _CC_HTTP_STATUS_ESTABLISHED_;
}
// 分配POST缓冲区
if (http->buffer.bytes == nullptr && http->payload > 0) {
_cc_alloc_buf(&http->buffer, (size_t)http->payload);
}
}
// 处理POST负载数据
if (http->state == _CC_HTTP_STATUS_PAYLOAD_) {
_cc_buf_append(&http->buffer, io->r.bytes, io->r.off);
if (http->buffer.length >= http->payload) {
http->state = _CC_HTTP_STATUS_ESTABLISHED_;
}
io->r.off = 0;
}
// 请求处理完成
if (http->state == _CC_HTTP_STATUS_ESTABLISHED_) {
// 根路径请求
if (http->request->script[0] == '/' && http->request->script[1] == 0) {
request_ok(e, io);
}
// 文件请求
else {
tchar_t www_file[_CC_MAX_PATH_] = {0};
_stprintf(www_file, _T("%s%s"), root, http->request->script);
if (_taccess(www_file, _CC_ACCESS_F_) == 0) {
request_file(e, io, www_file);
} else {
not_found(e, io);
}
}
// 重置状态并记录日志
http->state = _CC_HTTP_STATUS_HEADER_;
_cc_logger_info("http:%s %s %s",
http->request->method,
http->request->script,
http->request->protocol);
// 记录POST原始数据
if (_tcsicmp(http->request->method, _T("POST")) == 0) {
_cc_logger_info("RAW:%.*s", http->buffer.length, http->buffer.bytes);
}
// 释放请求头
_cc_http_free_request_header(&http->request);
}
return true;
}
数据发送处理
/**
* 数据发送回调
* @param async 异步事件管理器
* @param e 事件对象
* @return 是否继续发送
*/
static bool_t onWrite(_cc_async_event_t *async, _cc_event_t *e) {
_http_t *http = (_http_t*)e->data;
_cc_io_buffer_t *io = http->io;
_cc_logger_debug(_T("%d onWrite."), e->ident);
// 刷新缓冲区数据
if (io->w.off) {
if (_cc_io_buffer_flush(e, http->io) < 0) {
return false; // 发送失败
}
}
// 发送文件内容
if (http->file) {
int32_t off = (int32_t)_cc_fread(
http->file,
io->w.bytes + io->w.off,
1,
io->w.limit - io->w.off
);
if (off <= 0) {
_cc_fclose(http->file);
http->file = nullptr;
} else {
io->w.off += off;
_CC_SET_BIT(_CC_EVENT_WRITABLE_, e->flags);
}
}
return true;
}
连接超时处理
/**
* 连接超时回调
* @param async 异步事件管理器
* @param e 事件对象
* @return 是否保持连接
*/
static bool_t onTimeout(_cc_async_event_t *async, _cc_event_t *e) {
_cc_logger_debug(_T("%d onTimeout."), e->ident);
return false; // 返回false表示关闭连接
}
HTTP 服务监听
/**
* 启动HTTP监听
* @param host 监听地址(nullptr表示任意地址)
* @param port 监听端口
* @return 是否成功
*/
bool_t addListener(const tchar_t *host, uint16_t port) {
struct sockaddr_in sa;
_cc_async_event_t *async = _cc_get_async_event();
_cc_event_t *event = _cc_event_alloc(async, _CC_EVENT_ACCEPT_);
_cc_assert(async != NULL);
_cc_assert(event != NULL);
if (event == nullptr) {
return false;
}
// 配置事件参数
event->timeout = 60000; // 60秒超时
event->callback = doEvent;
// 绑定地址和端口
_cc_inet_ipv4_addr(&sa, host, port);
if (!_cc_tcp_listen(async, event, (_cc_sockaddr_t *)&sa, sizeof(struct sockaddr_in))) {
_cc_free_event(async, event);
_cc_assert(FALSE);
return false;
}
return true;
}
主函数
/**
* 程序入口
* @return 退出码
*/
int main() {
int c;
// 初始化异步事件系统
_cc_alloc_async_event(0, nullptr);
// 启动HTTP监听(8080端口)
addListener(nullptr, 8080);
// 主循环(按q键退出)
while((c = getchar()) != 'q') {
_cc_sleep(100); // 100ms延迟
}
// 释放资源
_cc_free_async_event();
return 0;
}