NGINX 如何根据客户端协议构造并返回响应
NGINX 如何根据客户端协议构造并返回响应
Nginx 向客户端返回响应时,会根据客户端使用的协议类型,构造对应格式的响应数据。为了实现这一点,Nginx 在响应阶段设置了一系列 filter(过滤器)模块,每个 filter 对应特定协议或处理职责。
当响应准备就绪后,这些 filter 会按照链式结构依次执行。Nginx 会根据请求上下文中的 r
(ngx_http_request_t
)结构体中的属性,判断当前客户端使用的协议,并选择是否调用某个具体的 filter:
- 对于 HTTP/1.1 请求,会调用
ngx_http_header_filter
生成 HTTP/1.x 的响应头; - 对于 HTTP/2 请求,则会调用
ngx_http_v2_header_filter
来构造 HTTP/2 格式的头部帧。
这些 r
的属性值是在客户端连接建立并解析请求时确定的。因此,响应阶段可以据此区分协议类型,选择合适的响应构建路径。
本文的主题是:
Nginx 是如何根据客户端所用协议,在响应阶段构造并发送符合该协议格式的响应的?
即下图第④步
1 |
|
1、header
响应头构建的整体流程:
1 |
|
ngx_http_send_header(r)
会触发从ngx_http_top_header_filter
开始的 header filter 链,每个 filter 调用下一个,直到最终的 HTTP/1.x 或 HTTP/2 的 header_filter 执行完毕- 响应体部分则由
ngx_http_output_filter(r, cl)
依次经过 body filter,最终输出到客户端
协议差异详解
步骤 | HTTP/1.x | HTTP/2 |
---|---|---|
协议识别 | r->http_version 为 1.0/1.1 |
r->stream 非 NULL |
最终 header_filter | ngx_http_header_filter() |
ngx_http_v2_header_filter() |
头部格式 | 文本:HTTP/1.1 200 OK\r\nHeader: value\r\n\r\n |
二进制 HPACK 编码的 HEADERS 帧(含伪头字段) |
体部发送 | 按块写入 socket,可使用 chunked 编码 |
通过 DATA 帧发送,支持流控与多路复用 |
连接管理 | 基于 Connection /Keep-Alive 头部 |
内建于 HTTP/2 的连接与流模型中 |
特殊字段处理 | Transfer-Encoding: chunked 等 |
伪头字段如 :status 、:content-type 等 |
- HTTP/1.x 在
ngx_http_header_filter()
中按 RFC 7230 拼接纯文本头部,并调用写过滤器输出 - HTTP/2 则在
ngx_http_v2_header_filter()
中将headers_out
转为 HPACK 编码,并封装为HEADERS
帧发送
核心代码
HTTP/1.x 的 header filter
1 |
|
HTTP/2 的 header filter
1 |
|
Nginx 通过统一的 filter 链机制,在尾部根据
r->http_version
或r->stream
判断客户端协议,选择合适的header_filter
(文本 vs 帧格式)构造响应头,再由 output filter 发送响应体。
2、body
Nginx 如何根据客户端协议生成并发送响应体
整体流程
1 |
|
协议差异详解
步骤 | HTTP/1.x | HTTP/2 |
---|---|---|
协议识别 | r->http_version == NGX_HTTP_VERSION_11 (值 1001) |
r->stream != NULL |
最终写入函数 | ngx_http_write_filter() |
ngx_http_v2_write_filter() |
数据格式 | 纯文本,可使用 Content-Length 或 Transfer-Encoding: chunked |
HTTP/2 DATA 帧,内建流控与多路复用 |
分块策略 | 若无 Content-Length ,自动切分为 chunked 块 |
自动分帧(每帧通常 ≤16 KB),由帧头声明长度 |
连接管理 | 结束时依据 Connection: keep-alive 决定连接是否复用 |
通过 END_STREAM 标志结束流,无需单独的 Connection 头部 |
调用链入口 | ngx_http_output_filter() → ngx_http_write_filter() |
ngx_http_output_filter() → ngx_http_v2_write_filter() |
Filter 链机制
- ngx_http_output_filter
统一入口,将响应体推入 body_filter 链。 - gzip_body_filter
若开启 gzip,则对数据压缩。 - range_body_filter
若是 Range 请求,则截取对应区间。 - chunked_body_filter
在 HTTP/1.1 无Content-Length
时,添加 chunked 编码。 - write_filter
最终输出:HTTP/1.x 调用ngx_http_write_filter()
,HTTP/2 则走ngx_http_v2_write_filter()
。
HTTP/1.x 下的输出
1 |
|
- Chunked 编码:在无明确长度时,插入
<len>\r\n<data>\r\n0\r\n\r\n
。 - 写入方式:通过
send()
或缓冲区累积后写出。
HTTP/2 下的输出
1 |
|
- DATA 帧:每个帧包含长度、标志位和负载,完全由 HTTP/2 规范管理。
- 流控和多路复用:Nginx 底层处理,不需额外编码。
Nginx 在输出响应体时,通过统一的 body_filter 链完成压缩、缓存、限速等处理,最后根据
r->stream
或r->http_version
选择协议专属的 write_filter,将数据以文本或帧的形式写回客户端。
3、整体响应转发流程
3.1 原理概述
当 Nginx 向 upstream 发起请求后,会将该连接的读事件处理函数(通常为 ngx_http_upstream_process_header()
)注册到事件模块(如 epoll)中。一旦 upstream 返回响应,即由事件驱动机制触发该处理函数,开始解析并处理响应。整个流程可分为:从读取 upstream 响应,到构造并发送给客户端的完整闭环。
3.2 详细步骤
1. 读取并解析 Upstream 响应
- 异步读取响应头
ngx_http_upstream_process_header()
负责循环读取 upstream 返回的数据,直至完整解析出状态行与响应头字段;- 解析结果存储在
ngx_http_upstream_t
的相关数据结构中。
- 发送响应头到下游
- 解析完毕后,调用
ngx_http_upstream_send_response()
,将 upstream 头部信息填充至r->headers_out
; - 随后调用
ngx_http_send_header(r)
,触发 header filter 链,最终由协议对应的 header_filter 输出响应头。
- 解析完毕后,调用
- 读取并转发响应体
- 上游的响应体数据通过
ngx_http_upstream_send_response()
逐块传递给下游; - 响应体会在
ngx_http_output_filter(r, body_chain)
中经过 body filter 链(gzip、限速、chunked 等),并最终由协议专属的 write_filter 写出。
- 上游的响应体数据通过
2. 通过 Filter 链发送给客户端
- 发送响应头
ngx_http_send_header(r)
检查头部是否已发送,若未发送则执行ngx_http_top_header_filter(r)
;- 对于 HTTP/1.x,最终调度到
ngx_http_header_filter()
; - 对于 HTTP/2,则由
ngx_http_v2_header_filter()
构造 HPACK 编码的 HEADERS 帧。
- 发送响应体
ngx_http_output_filter(r, body_chain)
启动 body filter 链;- 经过
gzip_body_filter
、range_body_filter
、chunked_body_filter
等处理后,最终调用:- HTTP/1.x 情况下的
ngx_http_write_filter()
,通过ngx_chain_writer()
按需添加 chunked 编码或直接写入 socket; - HTTP/2 情况下的
ngx_http_v2_write_filter()
,将每个 buffer 封装为 DATA 帧,自动处理分帧及流控。
- HTTP/1.x 情况下的
3. 完成请求与连接管理
- 请求终结
- 当所有数据传输完毕后,调用
ngx_http_finalize_request(r, rc)
,清理请求上下文并释放资源;
- 当所有数据传输完毕后,调用
- 连接复用或关闭
- 若请求使用 HTTP/1.1 或 HTTP/2 并且支持 Keep-Alive(或多路复用),则保持连接或流打开,等待后续请求;
- 若收到
Connection: close
指令,或 Keep-Alive 不可用,则关闭连接或流。
Nginx 在整个转发过程中,通过统一的 upstream 读事件回调获取上游数据,再通过 header/body filter 链和协议专属的 header_filter/write_filter,完成对不同协议(HTTP/1.x 与 HTTP/2)响应格式的动态构建与发送。