大家好,我是杂烩君。今天给大家带来一篇干货满满的技术文章——深入解析evhtp HTTP服务器中的Hook机制。
嵌入式中,我们经常需要在不修改核心代码的前提下扩展功能,而Hook机制恰好就是解决这个问题的利器。
1. Hook机制是什么?
Hook,中文翻译为"钩子",形象地描述了它的作用——在程序执行流程的关键节点"挂上"自定义的处理函数。
打个通俗的比方:假如你在工厂流水线上工作,每个产品经过你的工位时,你可以检查质量、添加零件或者记录日志。Hook机制就像是在软件的"流水线"上预留的工位,让你可以插入自己的处理逻辑。
使用Hook机制带来的好处:
低耦合:核心业务代码和扩展功能分离
可扩展:新增功能无需修改原有代码
可复用:Hook函数可在多个场景复用
易维护:功能模块化,定位问题更快
性能可控:按需启用Hook,零成本关闭
2. evhtp的Hook示例
关于evhtp的介绍可看往期文章:一款专为嵌入式系统设计的开源HTTP库——libevhtp
下面给出一个完整的可运行示例,演示如何使用evhtp的Hook机制:
#include <evhtp.h>
#include <sys/queue.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
// Hook函数:记录请求开始
evhtp_res on_headers_start(evhtp_request_t *req, void *arg)
{
printf("[%ld] 开始解析请求头n", time(NULL));
fflush(stdout);
return EVHTP_RES_OK;
}
// Hook函数:检查每个header
evhtp_res on_header(evhtp_request_t *req, evhtp_header_t *hdr, void *arg)
{
printf(" Header: %s = %sn", hdr->key, hdr->val);
fflush(stdout);
return EVHTP_RES_OK;
}
// Hook函数:请求头解析完成
evhtp_res on_headers(evhtp_request_t *req, evhtp_headers_t *hdrs, void *arg)
{
// 手动统计header数量
int count = 0;
evhtp_header_t *header;
TAILQ_FOREACH(header, hdrs, next) {
count++;
}
printf("[%ld] 请求头解析完成,共 %d 个n", time(NULL), count);
fflush(stdout);
return EVHTP_RES_OK;
}
// Hook函数:接收body数据
evhtp_res on_body(evhtp_request_t *req, evbuf_t *buf, void *arg)
{
size_t len = evbuffer_get_length(buf);
printf(" 收到Body数据: %zu 字节n", len);
fflush(stdout);
return EVHTP_RES_OK;
}
// 业务回调函数
void request_handler(evhtp_request_t *req, void *arg)
{
printf("[业务处理] %sn", req->uri->path->full);
fflush(stdout);
evbuffer_add_printf(req->buffer_out, "Hello from evhtp!n");
evhtp_send_reply(req, EVHTP_RES_OK);
}
int main(int argc, char **argv)
{
struct event_base *evbase = event_base_new();
evhtp_t *htp = evhtp_new(evbase, NULL);
printf("================= Embedded HTTP Server Demo =================n");
// 设置业务回调
evhtp_set_cb(htp, "/api/test", request_handler, NULL);
// 获取回调并设置Hook
evhtp_callback_t *cb = evhtp_get_cb(htp, "/api/test");
evhtp_callback_set_hook(cb, evhtp_hook_on_headers_start,
(evhtp_hook)on_headers_start, NULL);
evhtp_callback_set_hook(cb, evhtp_hook_on_header,
(evhtp_hook)on_header, NULL);
evhtp_callback_set_hook(cb, evhtp_hook_on_headers,
(evhtp_hook)on_headers, NULL);
evhtp_callback_set_hook(cb, evhtp_hook_on_read,
(evhtp_hook)on_body, NULL);
evhtp_bind_socket(htp, "0.0.0.0", 8080, 1024);
printf("server start at: http://0.0.0.0:8080n");
event_base_loop(evbase, 0);
evhtp_free(htp);
event_base_free(evbase);
return0;
}
编译运行:
gcc -o hook_demo hook_demo.c -levhtp -levent
./hook_demo
测试指令:
curl -X POST http://localhost:8080/api/test -d 'hello'
测试输出:
3. evhtp的Hook实现原理
接下来我们深入源码,看看evhtp是如何实现Hook机制的。
3.1 Hook数据结构
evhtp的Hook存储结构如下:
evhtp提供了丰富的Hook点覆盖整个请求处理流程:
| Hook类型 | 触发时机 | 常用场景 |
|---|---|---|
on_headers_start |
开始解析请求头 | 请求预处理、计时开始 |
on_header |
解析每个请求头 | Header校验、日志记录 |
on_headers |
所有请求头解析完成 | 认证鉴权、请求路由 |
on_path |
URL路径解析完成 | 路径重写、权限检查 |
on_read |
接收到Body数据 | 数据校验、流式处理 |
on_request_fini |
请求处理完成 | 资源清理、统计上报 |
on_connection_fini |
连接关闭 | 连接池管理、清理 |
on_connection_error |
连接错误 | 连接错误处理 |
on_error |
发生错误 | 错误处理、告警 |
on_new_chunk |
新chunk开始 | Chunked编码处理 |
on_chunk_fini |
Chunk完成 | Chunk数据处理 |
on_chunks_fini |
所有Chunks完成 | 完整Body处理 |
on_hostname |
主机名解析完成 | 虚拟主机路由 |
on_write |
写入数据 | 响应拦截、日志 |
on_event |
事件触发 | 事件处理 |
3.2 Hook调用机制
evhtp定义了两个宏来统一Hook调用逻辑。一个支持可变参数,一个不支持额外参数。
这两个宏调最终调用的就是上层设置的Hook函数。这两个宏实现了两层Hook查找:
- 先查找request级别的Hook如果没有,再查找connection级别的Hook
3.3 Hook设置函数
evhtp提供了统一的Hook设置接口:
3.4 Hook实际调用点
以header解析为例:
对应的Hook调用函数:
3.5 完整的Hook调用流程
在HTTP请求处理过程中,Hook的调用顺序如下:
4. 总结
Hook机制是软件设计中的重要模式,尤其适合嵌入式系统中需要灵活扩展的场景。通过evhtp的实现,我们学到了:Hook机制通过函数指针实现在关键节点插入自定义逻辑、丰富的Hook点覆盖了HTTP请求处理的全生命周期。
113