nginx通过事件驱动异步接收post请求体:先注册读事件,再分批调用recv()读取,每次最多8kb,就绪才读,eagain则挂起;小体入内存缓冲,大体写临时文件,chunked边收边解析;收完后触发post_handler交由上层模块处理。

POST请求体的接收起点:从事件驱动到读事件触发
当客户端发起POST请求,Nginx在完成请求行和头部解析后,若发现存在 Content-Length 或使用 Transfer-Encoding: chunked,就会进入请求体读取流程。此时请求状态设为 NGX_HTTP_READING_REQUEST_BODY,但**并不立即读取全部Body**,而是注册一个读事件监听器(如 ngx_http_read_client_request_body),等待内核通知连接可读——这是异步性的根本来源。
真正读取的入口:ngx_http_do_read_client_request_body
该函数是实际执行读操作的核心,但它本身不阻塞。它会反复调用 ngx_http_read_unbuffered_request_body(针对未缓冲模式)或 ngx_http_read_client_request_body(缓冲模式),每次仅尝试从连接中读取最多 c->recv_buffer_size 字节(默认 8KB)。读完一批后,若未收完,就再次调用 ngx_add_timer 设置超时,并返回 NGX_AGAIN,让事件循环继续调度其他请求。
关键点在于:
- 每次 recv() 调用前都检查 c->read->ready,仅在就绪时才真正读;
- 若返回 EAGAIN/EWOULDBLOCK,说明内核缓冲区暂空,就挂起等待下次可读事件;
- 若读到EOF或错误,走异常路径(如关闭连接或返回400)。
缓冲与分块处理:内存管理与回调机制
对于小Body(client_max_body_size 内且小于 client_body_buffer_size),Nginx直接分配内存缓冲区(r->request_body->buf)存放;大Body则写入临时文件(client_body_temp_path),并维护一个 ngx_chain_t 链表记录各段位置。
整个过程由回调驱动:
- 每次成功读取一段数据,就调用用户配置的 post_handler(如 ngx_http_proxy_pass_request_body);
- 若启用 client_body_in_file_only on,则全程不进内存,只写磁盘;
- Chunked 编码由 ngx_http_parse_chunked 在读取过程中边收边解析,不等全部收完。
异步完成的标志:ngx_http_request_body_filter 与完成回调
当所有Body确认收完(包括最后的0-length chunk或精确匹配 Content-Length),Nginx 执行 ngx_http_do_read_client_request_body 的收尾逻辑:
- 设置 r->request_body->rest = 0;
- 将状态改为 NGX_HTTP_REQUEST_BODY_READ;
- 调用 r->request_body->post_handler(r),把控制权交还给上层模块(如 proxy、fastcgi)。
这个 post_handler 是真正的业务入口——例如 proxy 模块此时才开始构造上游请求,而此前整个 Body 接收完全不阻塞 worker 进程处理其他连接。











