Open vislee opened 1 year ago
http3.0 HEADERS帧Lua用例代码:
local function parse_headers_frame(frame_data)
-- 读取帧标志
local flags = string.byte(frame_data, 1)
-- 检查是否存在头部碎片
local has_fragment = bit32.band(flags, 0x01) ~= 0
if has_fragment then
-- TODO: 处理头部碎片的情况
end
-- 获取头部长度
local tmp = frame_data:sub(2, 3)
local length = string.unpack(">I2", tmp)
print("length:", length)
-- 解析头部
local headers = {}
local pos = 4
while pos <= length do
-- 读取头部名长度
local name_len = string.unpack(">I2", frame_data:sub(pos, pos+1))
print("name_len:", name_len)
pos = pos + 2
-- 读取头部名
local name = frame_data:sub(pos, pos+name_len-1)
pos = pos + name_len
-- 读取头部值长度
local value_len = string.unpack(">I2", frame_data:sub(pos, pos+1))
print("value_len:", value_len)
pos = pos + 2
-- 读取头部值
local value = frame_data:sub(pos, pos+value_len-1)
pos = pos + value_len
-- 添加头部到结果列表
table.insert(headers, {name = name, value = value})
end
return headers
end
-- 测试示例
local headers_frame_data = "\x01\x00\x0c\x00\x07example\x00\x0bexample.com"
local headers = parse_headers_frame(headers_frame_data)
for _, header in ipairs(headers) do
print("Name:", header.name)
print("Value:", header.value)
end
概述
http3并没有改变http1的语义:
只是改变了编码格式和传输:
(图片来自:https://blog.cloudflare.com/http3-the-past-present-and-future/)
在UDP报文头部与HTTP消息之间,共有3层头部
这3层Header实现的功能各不相同:
Package 报文
协商连接ID
QUIC 帧
STREAM Frame { Type (i) = 0x08..0x0f, // Stream ID (i), // 流ID 一个可变长度的整数,表示流的流ID [Offset (i)], // 一个可变长度整数,指定此 STREAM 帧中数据在流中的字节偏移量。当 OFF 位设置为 1 时,该字段存在。当 Offset 字段不存在时,偏移量为 0 [Length (i)], // 一个可变长度整数,指定此 STREAM 帧中流数据字段的长度。当 LEN 位设置为 1 时,该字段存在。当 LEN 位设置为 0 时,流数据字段消耗数据包中的所有剩余字节 Stream Data (..), }
HTTP3 帧
握手
nginx源码
介绍: https://nginx.org/en/docs/quic.html
listen 8443 quic reuseport;
通过示例可看出listen 增加了quic的选项。 代码ngx_http_core_listen
函数:启动nginx初始化完配置后在
ngx_worker_process_init
函数调用了所有模块的init_process
回调函数,会调用到“event_core”的ngx_event_process_init
函数,在该函数添加listen回调函数并添加到epoll中。ngx_quic_recvmsg
函数就是nginx处理quic的入口函数了。// ngx_event_quic_transport.h
// ngx_event_quic_transport.c
ngx_quic_recvmsg
函数中调用recvmsg
接收消息,调用ngx_get_connection
分配连接结构体,回调ngx_http_init_connection
函数,如果是quic协议则调用ngx_http_v3_init_stream
初始化流。代码里随处可见的,获取ngx_quic_connection_t
#define ngx_quic_get_connection(c) \ (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL)
在ngx_quic_open_sockets
函数中,先调用了ngx_quic_create_socket
创建quic socket,结构如下,在ngx_quic_listen
函数对quic socket结构体赋值,最后再把qsock的udp赋给ngx_connection_t
结构体的udpc->udp = &qsock->udp;
因此,udp必须是ngx_quic_socket_s结构体的第一个字段,udp的地址和ngx_quic_socket_s的内存地址是一样,所以可以这样(ngx_quic_socket_t *)((c)->udp)
强制转换后取quic,该quic也就是ngx_quic_listen
函数中qsock->quic = qc;
的值。// ngx_event_quic_socket.c
// ngx_event_quic_transport.c // 解析quic帧,只保留了STREAM帧的处理。
总结
// 核心处理函数 // 接收udp数据报, 每个UDP报都需要经过该函数的处理 ngx_quic_recvmsg
// 从quic Package中获取连接ID,相同ID标识一条quic虚拟的连接。
ngx_quic_get_packet_dcid
// 根据连接ID获取已建好的连接,连接是ngx_quic_listen函数插入的。
ngx_quic_lookup_connection
// 分配一组连接所需要的connection结构体,quic和h3代码中,只有两个地方分配connection了
// 1.在ngx_quic_recvmsg中,为每个quic虚拟连接分配一个。
// 2.在ngx_quic_create_stream函数中,为每个stream分配一个。
ngx_get_connection
// quic启动函数,需要关注的是读回调:c->read->handler = ngx_quic_input_handler;
ngx_quic_run
// 处理UDP数据报,该函数从ngx_quic_get_connection是否有返回分为两种情况。
ngx_quic_handle_datagram
// 处理quic Packet
ngx_quic_handle_packet
// 解析quic Packet
ngx_quic_parse_packet
// 新建quic connection,一个连接ID标识一个quic connection,一个quic connection可以支持多stream。
ngx_quic_new_connection
// 在quic connection上抽象出quic socket,
ngx_quic_open_sockets ngx_quic_listen
// 处理quic frame
ngx_quic_handle_frames
// 解析quic frame ngx_quic_read_bytes 读取quic frame携带的数据
ngx_quic_parse_frame
// 处理stream frame, ngx_quic_parse_frame 解析得到的数据调用 ngx_quic_write_buffer 缓存到qs->recv
ngx_quic_handle_stream_frame
// 获取对应流ID的stream, 流ID低2位是标志位,所以会有 id >> 2、 min_id += 0x04 这样的操作。
ngx_quic_get_stream (rev->handler = ngx_quic_init_stream_handler;)
// 新建一个stream,对应一个虚拟连接stream connection
ngx_quic_create_stream {sc->recv = ngx_quic_stream_recv;}
// 添加到事件驱动中,添加到 ngx_posted_events 全局队列等待调度执行
ngx_quic_set_event
// 解析http3的HEADERS帧
ngx_http_v3_parse_headers
// 解析http3的DATA帧
ngx_http_v3_parse_data
// 根据索引搜索QPACK静态索引表,静态表是全局的
ngx_http_v3_lookup_static
// 根据索引搜索QPACK动态索引表,动态表是基于连接的
ngx_http_v3_lookup
// 处理http3的一个请求头KV
ngx_http_v3_process_header
// 处理伪头,也就是请求行内容(method、uri、schema、host等)
ngx_http_v3_process_pseudo_header
// 初始化quic connection // quic Packet 一个连接ID标识一条连接 ngx_quic_recvmsg -> ngx_http_init_connection[ls->handler] -> ngx_http_v3_init_stream -> ngx_quic_run{c->read->handler = ngx_quic_input_handler;} -> ngx_quic_handle_datagram -> ngx_quic_handle_packet{ngx_quic_new_connection; ngx_quic_handle_payload} -> ngx_quic_new_connection -> ngx_quic_open_sockets -> ngx_quic_listen{qsock->udp.connection = c; ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node);} -> ngx_quic_handle_payload -> ngx_quic_handle_frames -> ngx_quic_handle_stream_frame -> ngx_quic_set_event // 处理后续udp报 ngx_quic_recvmsg {ngx_quic_lookup_connection; ngx_quic_get_socket;} -> ngx_quic_input_handler[rev->handler(rev)] -> ngx_quic_handle_datagram -> ngx_quic_handle_packet -> ngx_quic_handle_payload -> ngx_quic_handle_frames -> ngx_quic_handle_stream_frame -> ngx_quic_write_buffer // 处理连接上的quic frame ngx_process_events_and_timers -> ngx_event_process_posted -> ngx_quic_init_stream_handler[rev->handler] -> ngx_http_init_connection[ls->handler] -> ngx_http_v3_init_stream -> ngx_http_v3_init_request_stream -> ngx_http_v3_wait_request_handler -> ngx_http_v3_process_request -> ngx_http_process_request // 读取http3的body帧并解析payload ngx_http_read_client_request_body -> ngx_http_v3_read_request_body{r->read_event_handler = ngx_http_v3_read_client_request_body_handler;} -> ngx_http_v3_request_body_filter -> ngx_http_v3_parse_data
// 核心结构 ngx_http_v3_session_t // quic connection 一个连接ID对应一个结构,支持多个stream ngx_quic_connection_t // 基于connection的socket ngx_quic_socket_t // 一条stream对应一个该结构体 ngx_quic_stream_t
参考
https://www.taohui.tech/2021/02/04/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/%E6%B7%B1%E5%85%A5%E5%89%96%E6%9E%90HTTP3%E5%8D%8F%E8%AE%AE/ https://www.zhihu.com/tardis/zm/art/228723188?source_id=1003 https://datatracker.ietf.org/doc/id/draft-ietf-quic-transport-29.html https://my.oschina.net/u/4273516/blog/8597013 检查是否支持Http3: https://http3check.net/