ngx_http_request_t 是持续存活、状态可变的运行时上下文,贯穿TCP连接生命周期,支持挂起恢复、子请求和非阻塞I/O;不能当作一次性对象释放,其字段构成轻量级状态机,依赖core层统一管理生命周期。

ngx_http_request_t 不是“请求的快照”,而是异步非阻塞处理中持续存活、状态可变的运行时上下文。它让单个 worker 进程能同时挂起、恢复、重入成百上千个请求,而无需为每个请求分配独立线程或阻塞等待 I/O。
为什么不能把 ngx_http_request_t 当作一次性对象?
很多初看源码的人会误以为一个 HTTP 请求进来就 new 一个 ngx_http_request_t,处理完就 free —— 这在同步模型里成立,但在 Nginx 里完全错误。
-
ngx_http_request_t在连接建立后即分配,生命周期贯穿整个 TCP 连接(支持 keepalive),可能服务多个 HTTP 请求 - 它内部包含大量指针字段(如
connection、pool、headers_in、out链表等),指向的是长期复用的内存池或连接上下文,不是栈上临时变量 - 当遇到 upstream 转发、子请求、延迟响应等场景时,Nginx 会主动挂起该结构体(比如把
write_event_handler设为ngx_http_block_reading),等事件就绪再回调恢复,此时结构体必须保持完整且可寻址
ngx_http_request_t 如何配合 epoll 实现非阻塞读写?
Nginx 不靠操作系统自动调度请求,而是靠 ngx_http_request_t 显式记录每个请求当前卡在哪一步、等什么事件、下次该调谁。
- 读事件触发时,
ngx_http_process_request_line或ngx_http_process_request_headers被调用;若一次没读完(比如只收到半行),它不会阻塞,而是把read_event_handler设为ngx_http_block_reading,并把连接保留在 epoll 的监听集合中 - 写事件触发时,如果发送缓冲区满(
sendfile返回EAGAIN或writev返回部分字节数),Nginx 把未发送完的out链表挂在request->out上,并注册写事件回调,不等数据发完就返回去处理其他请求 - 所有这些“挂起-恢复”逻辑都依赖
ngx_http_request_t中的字段:比如write_event_handler、read_event_handler、blocked、postponed、waited等,它们共同构成一个轻量级的状态机
常见误用:ngx_http_request_t * 被提前释放或跨 event loop 访问
这是模块开发中最容易崩溃的点。一旦你手动调用 ngx_pfree 或清空 request 所属内存池,而 epoll 还在监听这个连接,后续事件回调再访问该 request 就是野指针。
- 永远不要在 handler 或 filter 模块中直接
ngx_pfree(request->pool)—— request 生命周期由 core 层统一管理,通常在ngx_http_close_request中释放 - 子请求(subrequest)共享父请求的连接和 pool,但有自己的
ngx_http_request_t实例;务必用ngx_http_subrequest创建,而非手动 malloc - 在定时器回调(如
ngx_add_timer)中访问request前,先检查request->connection->fd != (ngx_socket_t) -1和request->pool != NULL,否则连接可能已被关闭
真正难的不是理解 ngx_http_request_t 有哪些字段,而是意识到它是一个被反复读写、跨多次 epoll_wait 调度、承载状态迁移的“活对象”。任何试图把它当成纯数据结构来拷贝、序列化或短期持有的做法,都会在高并发下暴露竞态或崩溃。











