NGINX & HTTP2 初识

您所在的位置:网站首页 nginx代理http2 NGINX & HTTP2 初识

NGINX & HTTP2 初识

2024-01-15 23:38| 来源: 网络整理| 查看: 265

NGINX & HTTP2 初识

8月 11 NGINX HTTP, HTTP2, NGINX 评论 字数统计: 2k(字) 阅读时长: 8(分)

一 本文目的 学习 HTTP2 协议. 学习 NGINX 对 HTTP2 的处理以及如何从 HTTP2 进入 HTTP1.1 的处理流程. 未分析 HEADERS、DATA 之外帧处理. 二 协议概述

HTTP2 可以同时运行在 HTTP/HTTPS 之上, 在 HTTPS 上是通过 TLS 应用层协商协议(Application-Layer Protocol Negotiation 简称 ALPN)支持. NGINX 服务器是支持在 HTTP 下开启 HTTP2, 但是无法在同一个端口上同时支持 HTTP2、HTTP. 浏览器厂商选择只实现基于 HTTPS 的 HTTP2, 使用 ALPN 可以判断使用 HTTP/HTTP2.

HTTP2 协议特性:

二进制分帧协议

无队头堵塞: 可以并行发送多个 HTTP 请求, 不会触发队头阻塞.

多路复用

二进制流可以交错进行, 实现在单 TCP 连接上多路复用. 同时连接复用有效避免 TCP 慢启动、拥塞窗口协商过程, 提升传输效率. 可以将 Stream 想象为一系列 Frame 序列. 参见 Streams And Multiplexing.

服务端推送

头部压缩

头部压缩是有状态的, 在一个连接上只有一个压缩、解压上下文.

NGINX 默认没有编译 HTTP2 模块, 需要通过 --with-http_v2_module 选项开启.

协议细节参考 RFC 文档. 在规范中未定义 Frame ID 类似字段, 是通过 TCP 协议来确保同一个 Stream 中的 Frame 是有序的.

1. 交互顺序1234567891011121314sequenceDiagramparticipant C as Clientparticipant S as ServerC --> S : IdleC ->> S : HTTP2 前言, PRI * HTTP/2.0\r\n\r\nSM\r\n\r\nC ->> S : SETTINGSS ->> C : SETTINGS, HTTP2 连接建立成功C --> S : HEADERSC --> S : DATAC --> S : ...

协议规定, 对端响应的 SETTINGS 帧是前言的一部分, 因此 Server 必须发送 SETTINGS 帧.

对基于 HTTPS 的 HTTP2 在 SSL 握手阶段需要进行应用层协议协商(ALPN), 当 Sever 允许使用 HTTP2 时会响应 101 状态码进行协议切换, 会产生一次额外的 RTT.

Stream ID 约束: 客户端使用奇数流标识符; 服务端使用偶数流标识符. 特殊的 0 用于整个连接而非单独的流. 可以观察到 SETTINGS 帧的 Stream ID 都是 0.

流中可以有多个帧, 会出现多个帧具有相同的 Stream ID, 这些帧不会错乱是因为: 1. 发送方在同一个流中顺序发送; 2. TCP 协议确保帧会按发送顺序交付.

三 NGINX 中处理

对于 H2 处理有个比较重要的问题, 如何对一个 TCP 链接进行多路复用. NGINX 使用 fake_connection 与 H2 的 Stream 关联, 将其等价于 HTTP1.1 的 request 复用原有模块.

1. H2 入口

NGINX 中 HTTP2 协议处理有两个入口: 基于 HTTP 和基于 HTTPS.

当配置 http2 指令, 未开启 HTTPS 时, 会在 ngx_http_init_connection 阶段修改当前连接的接收处理函数为 ngx_http_v2_init 陷入 HTTP2 协议处理流程.

当开启 HTTPS 时, 会在 SSL 握手阶段, 根据请求协商信息确定是否启用 HTTP2.

2. H2 处理开始

NGINX 中 HTTP2 协议是由 ‘ngx_http_v2.c’ 模块实现.

在与客户端交互过程中, NGINX 会首先发送一个 SETTINGS 和 WINDOW_UPDATE 帧, 通知客户端 NGINX 支持的最大流数量、窗口大小、帧大小, 此时帧会先进行 queue 合并发送. TCP 连接的读/写事件处理函数为 ngx_http_v2_read_handler, ngx_http_v2_write_handler, HTTP2 状态机初始状态是 ngx_http_v2_state_preface.

ngx_http_v2_state_preface 检查发送“前言”是否正确, “前言”必须是 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n. 在“前言”处理完毕后会进入请求头处理 ngx_http_v2_state_head, 此时会根据帧首部 Type 使用不同的处理函数处理. 不同类型帧对应处理函数:

123456789101112static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { ngx_http_v2_state_data, /* NGX_HTTP_V2_DATA_FRAME */ ngx_http_v2_state_headers, /* NGX_HTTP_V2_HEADERS_FRAME */ ngx_http_v2_state_priority, /* NGX_HTTP_V2_PRIORITY_FRAME */ ngx_http_v2_state_rst_stream, /* NGX_HTTP_V2_RST_STREAM_FRAME */ ngx_http_v2_state_settings, /* NGX_HTTP_V2_SETTINGS_FRAME */ ngx_http_v2_state_push_promise, /* NGX_HTTP_V2_PUSH_PROMISE_FRAME */ ngx_http_v2_state_ping, /* NGX_HTTP_V2_PING_FRAME */ ngx_http_v2_state_goaway, /* NGX_HTTP_V2_GOAWAY_FRAME */ ngx_http_v2_state_window_update, /* NGX_HTTP_V2_WINDOW_UPDATE_FRAME */ ngx_http_v2_state_continuation /* NGX_HTTP_V2_CONTINUATION_FRAME */}; 3. 请求头处理

H2 同样遵循 HTTP1.1 的语义, 需要先发送请求行、请求头, 不过在 H2 中将请求行信息转换成特殊的请求头. 对于 HTTP1.1 请求行是 Method SP Request-URI SP HTTP-Version CRLF 格式, 在 H2 中会将其拆分成 :method, :path 请求头.

请求头是通过 ngx_http_v2_state_headers, ngx_http_v2_state_header_block, ngx_http_v2_state_field_len(ngx_http_v2_state_field_huff/ngx_http_v2_state_field_raw), ngx_http_v2_state_process_header, ngx_http_v2_state_header_complete 函数处理.

ngx_http_v2_state_headers

判断 StreamId 是否正确、请求头是否超限、流并发是否超限、分配 Stream 结构建立流依赖树.

ngx_http_v2_state_header_block

请求头处理, 有 5 种类型的请求头(具体看 HPACK 协议, 不展开). 根据请求头长度进行处理.

ngx_http_v2_state_field_len(ngx_http_v2_state_field_huff/ngx_http_v2_state_field_raw)

使用霍夫曼编码或原始编码解析请求头.

ngx_http_v2_state_process_header

对解析出来的请求头进行处理: 校验是否合法, 将其保存到 r->headers_in.headers 中.

ngx_http_v2_state_header_complete

判断是否有后续请求头, 进入 ngx_http_v2_state_header_block 继续处理. 如果设置标记 HEADERS 帧结束, 进入请求处理.

4. 请求处理

在 H2 请求头处理结束后会进入 ngx_http_v2_run_request 进行请求处理:

将 H2 格式请求信息转换成 HTTP1 格式信息(NGINX 内部使用, 能够复用 NGINX 原有功能); 进入 HTTP1 的请求处理函数 ngx_http_process_request, 调用 ngx_http_handler 运行 HTTP 处理的 11 个阶段.

在调用 ngx_http_v2_run_request 时使用的是 stream->request 作为参数, 是在 ngx_http_v2_state_headers 函数中创建的假的 request/connection, 读/写回调函数都是 ngx_http_v2_close_stream_handler.

在 ngx_http_process_request 中将请求读回调函数修改为 ngx_http_block_reading.

在 ngx_http_handler 中将请求写回调函数修改为 ngx_http_core_run_phases.

在 ngx_http_v2_read_request_body 中将读/写回调函数修改为 ngx_http_v2_read_client_request_body_handler/ngx_http_request_empty_handler.

5. 何时读取请求体?

前面已经提到在 ngx_http_handler 中会运行 HTTP 处理的 11 个阶段, 假设当前 location 用于反向代理那必定会运行 ngx_http_proxy_handler 函数. 可以追踪到函数调用链(注意, ngx_http_v2_read_client_request_body_handler 是通过回调触发):

1234567891011121314151617graph LRsubgraph HTTP 框架ngx_http_handler --> ngx_http_core_run_phasesngx_http_core_run_phases --> ...endsubgraph Proxy... --> ngx_http_proxy_handlerngx_http_proxy_handler --> ngx_http_read_client_request_bodyendsubgraph HTTP V2ngx_http_read_client_request_body --> ngx_http_v2_read_request_bodyngx_http_v2_read_request_body -.-> ngx_http_v2_read_client_request_body_handlerend

看到这里是不是会想 ngx_http_v2_read_client_request_body_handler 负责请求体读取操作操作? 跟进函数去没有读取操作, 而且函数参数 r->connection 并没有与 socket 关联无法进行读写操作.

对于 H2 数据是通过 DATA 帧进行传输, 还是得从 ngx_http_v2_frame_states 状态机跟进. ngx_http_v2_state_data 用于处理 DATA 帧, 其中调用链如下:

1234graph LRngx_http_v2_state_data --> ngx_http_v2_state_read_datangx_http_v2_state_read_data --> ngx_http_v2_process_request_bodyngx_http_v2_process_request_body --> post_handler

此处 post_handler 就是 ngx_http_upstream_init, 是 ngx_http_proxy_handler 中设置.

6. 响应

NGINX 响应分为 HEADER、BODY 两个阶段, 看下源码有 ngx_http_v2_filter_module 模块, 模块只介入 header_filter 处理阶段, 在其中以 H2 格式发送响应头, 有两点需要注意:

连接(connection)以及对应的套接字(socket):

对于 H2 在 header_filter/body_filter 的入参 request 是“假”的, 其关联的 connection 也是假的. 需要使用 H2 初始建立的连接, 通过 r->stream->connection 索引.

响应体处理:

H2 模块没有添加 body_filter 处理函数, 在 header_filter 阶段修改 connection 的 send_chain 回调函数为 ngx_http_v2_send_chain 用于 H2 响应体发送.

响应 Stream Id:

在响应阶段 request 是“假”的, 已经与请求 Stream 关联, 通过 r->stream->node->id 可以获得 sid.

这里只做了简略分析, 优先级、推送、窗口更新都没有提及.

四 抓包观察1. 配置

NGINX 可以不基于 HTTPS 启用 H2, 配置如下:

123456789101112131415server { listen 8000 default_server http2; location / { content_by_lua_block { ngx.log(ngx.ERR, "content phase") local content = "Our examples focus on using server push to improve page load performance in web browsers." for i = 1, 100 do ngx.say(content) ngx.sleep(0.1) end } }} 2. 请求

可以使用 CURL 发起 H2 请求:

1234567curl -i --http2-prior-knowledge http://127.0.0.1:8000/HTTP/2 200server: openresty/1.15.8.2date: Wed, 11 Aug 2021 01:17:25 GMTcontent-type: text/plaincontent phase

CURL 也支持发起 H3 请求, 方便抓包测试.

3. 抓包

客户端发送的 SETTINGS 帧信息

HTTP2 客户端发送 SETTINGS

服务端发送的 SETTINGS 帧信息

HTTP2 服务端发送 SETTINGS

注意, 服务端在响应 SETTINGS、WINDOW_UPDATE 帧时 Stream ID 都为 0; 在发送 HEADERS 帧 Stream ID 为 1, 是对客户端发起的 Stream ID 为 1 的响应. 即请求/响应在同一个流中进行.

五 参考文章 NGINX HTTP2 介绍 中文 HTTP2 介绍 HTTP/2 新特性浅析 HTTP2 RFC HTTP2 头部压缩 RFC HTTP2 头部压缩 RFC 中文 为 HTTP/2 头压缩专门设计的 HPACK


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3