NGINX 如何根据客户端协议构造并返回响应

NGINX 如何根据客户端协议构造并返回响应

Nginx 向客户端返回响应时,会根据客户端使用的协议类型,构造对应格式的响应数据。为了实现这一点,Nginx 在响应阶段设置了一系列 filter(过滤器)模块,每个 filter 对应特定协议或处理职责。

当响应准备就绪后,这些 filter 会按照链式结构依次执行。Nginx 会根据请求上下文中的 rngx_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
2
3
4
5
6
7
8
             
┌────────┐ ① ┌────────────┐ ② ┌────────┐
Client │ ⇄ protocol_xx ⇄ │ NGINX │ ⇄ protocol_xx ⇄ │ App
└────────┘ ④ └────────────┘ ③ └────────┘


协议转换(protocol_xx ⇄ protocol_xx)

1、header

响应头构建的整体流程:

1
2
3
4
5
6
7
8
9
请求进入 → upstream 响应返回 → 构造 headers_out

调用 ngx_http_send_header(r)

执行 ngx_http_top_header_filter(r)

沿 filter 链依次调用,最终由协议对应的 header_filter 完成响应头构造

响应体通过 ngx_http_output_filter(r, cl) 发送
  • 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->streamNULL
最终 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
2
3
4
5
6
7
static ngx_int_t
ngx_http_header_filter(ngx_http_request_t *r)
{
// 构造状态行和头部文本
// 写入 r->headers_out.headers
return ngx_http_next_header_filter(r);
}

HTTP/2 的 header filter

1
2
3
4
5
6
7
static ngx_int_t
ngx_http_v2_header_filter(ngx_http_request_t *r)
{
// HPACK 编码 headers_out
// 封装为 HEADERS 帧发送
return ngx_http_next_header_filter(r);
}

Nginx 通过统一的 filter 链机制,在尾部根据 r->http_versionr->stream 判断客户端协议,选择合适的 header_filter(文本 vs 帧格式)构造响应头,再由 output filter 发送响应体。

2、body

Nginx 如何根据客户端协议生成并发送响应体

整体流程

1
2
3
4
5
6
7
ngx_http_output_filter(r, body)

执行 ngx_http_top_body_filter(r, body)

沿 body_filter 链依次传递(如 gzip、缓存、限速 等)

最终由协议专属的 write_filter 将数据写入连接

协议差异详解

步骤 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-LengthTransfer-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 链机制

  1. ngx_http_output_filter
    统一入口,将响应体推入 body_filter 链。
  2. gzip_body_filter
    若开启 gzip,则对数据压缩。
  3. range_body_filter
    若是 Range 请求,则截取对应区间。
  4. chunked_body_filter
    在 HTTP/1.1 无 Content-Length 时,添加 chunked 编码。
  5. write_filter
    最终输出:HTTP/1.x 调用 ngx_http_write_filter(),HTTP/2 则走 ngx_http_v2_write_filter()

HTTP/1.x 下的输出

1
2
3
4
5
6
ngx_http_write_filter(r, chain)
{
// 如果有 Content-Length,按长度写入
// 否则自动插入 chunked 头尾
ngx_chain_writer(&writer_ctx, chain);
}
  • Chunked 编码:在无明确长度时,插入 <len>\r\n<data>\r\n0\r\n\r\n
  • 写入方式:通过 send() 或缓冲区累积后写出。

HTTP/2 下的输出

1
2
3
4
5
6
7
// 最终写出函数
ngx_http_v2_write_filter(r, chain)
{
// 将每个 buf 构造成 DATA 帧
// 自动分帧、设置流控窗口、标记 END_STREAM
ngx_http_v2_send_output_queue(r);
}
  • DATA 帧:每个帧包含长度、标志位和负载,完全由 HTTP/2 规范管理。
  • 流控和多路复用:Nginx 底层处理,不需额外编码。

Nginx 在输出响应体时,通过统一的 body_filter 链完成压缩、缓存、限速等处理,最后根据 r->streamr->http_version 选择协议专属的 write_filter,将数据以文本或帧的形式写回客户端。

3、整体响应转发流程

3.1 原理概述

当 Nginx 向 upstream 发起请求后,会将该连接的读事件处理函数(通常为 ngx_http_upstream_process_header())注册到事件模块(如 epoll)中。一旦 upstream 返回响应,即由事件驱动机制触发该处理函数,开始解析并处理响应。整个流程可分为:从读取 upstream 响应,到构造并发送给客户端的完整闭环。


3.2 详细步骤

1. 读取并解析 Upstream 响应

  1. 异步读取响应头
    • ngx_http_upstream_process_header() 负责循环读取 upstream 返回的数据,直至完整解析出状态行与响应头字段;
    • 解析结果存储在 ngx_http_upstream_t 的相关数据结构中。
  2. 发送响应头到下游
    • 解析完毕后,调用 ngx_http_upstream_send_response(),将 upstream 头部信息填充至 r->headers_out
    • 随后调用 ngx_http_send_header(r),触发 header filter 链,最终由协议对应的 header_filter 输出响应头。
  3. 读取并转发响应体
    • 上游的响应体数据通过 ngx_http_upstream_send_response() 逐块传递给下游;
    • 响应体会在 ngx_http_output_filter(r, body_chain) 中经过 body filter 链(gzip、限速、chunked 等),并最终由协议专属的 write_filter 写出。

2. 通过 Filter 链发送给客户端

  1. 发送响应头
    • 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 帧。
  2. 发送响应体
    • ngx_http_output_filter(r, body_chain) 启动 body filter 链;
    • 经过 gzip_body_filterrange_body_filterchunked_body_filter 等处理后,最终调用:
      • HTTP/1.x 情况下的 ngx_http_write_filter(),通过 ngx_chain_writer() 按需添加 chunked 编码或直接写入 socket;
      • HTTP/2 情况下的 ngx_http_v2_write_filter(),将每个 buffer 封装为 DATA 帧,自动处理分帧及流控。

3. 完成请求与连接管理

  1. 请求终结
    • 当所有数据传输完毕后,调用 ngx_http_finalize_request(r, rc),清理请求上下文并释放资源;
  2. 连接复用或关闭
    • 若请求使用 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)响应格式的动态构建与发送。

3.2、流程图

nginx返回响应整体流程


NGINX 如何根据客户端协议构造并返回响应
https://zjfans.github.io/2025/05/15/NGINX 如何根据协议构造并返回响应/
作者
张三疯
发布于
2025年5月15日
许可协议