☰
libwebsockets HTTP 流式压缩自测指南:api-test-http-compression 深度解析
2026/10/13 1:50:33 网站建设 项目流程
  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

导读

本文以 libwebsockets 仓库中的自测程序lws-api-test-http-compression(README.md)为主线,完整剖析 lws 服务端 HTTP 流式压缩(deflate与可选br)的协商、编码、传输与端到端校验全过程。读者将理解accept-encoding协商规则、lws 仅压缩文本类 MIME 的判定逻辑、zlib/brotli 底层实现细节,并掌握该测试程序的构建、运行与 ctest 集成方式。

测试程序定位:单进程端到端完整性验证

lws-api-test-http-compression是一个单进程自测程序,核心价值在于验证"HTTP 流式压缩能否完整无损地传输文件"。其设计思路(见 main.c 头部注释)非常清晰:

  • 文件挂载(file mount)在本地生成一个多 MB 级文本文件,文件名就是文件内容的sha256 十六进制摘要,例如/3a7bd3e2...c1f0.txt;
  • 客户端随后分别通过h1和h2c(prior knowledge,无 TLS)抓取该文件,并携带多种accept-encoding请求头;
  • 客户端用zlib 或 brotli解码收到的响应体,再对比"解码后长度"与"sha256 摘要"是否与文件名一致;
  • 只要压缩传输发生任何损坏、截断或分帧错误,都会表现为摘要不匹配,而不会"静默成功"。

正因为文件名与内容摘要强绑定,该程序把"服务端压缩是否正确"这一行为转化为可自动判定的布尔结果,是一种极其可靠的压缩正确性验证手段。

测试矩阵:accept-encoding 的协商预期

原文档给出了核心测试矩阵,程序实际执行的用例(main.c 中的cases[]表)比文档表格更完整,此处合并呈现:

请求(accept-encoding)期望响应(content-encoding)说明
不发送(h1/h2)identity(无该头)不协商压缩
gzipidentity(lws 对普通文件不提供 gzip)见下文 gzip 说明
deflatecontent-encoding: deflate(raw deflate 流)zlib 格式(RFC 1950)
deflate;q=0identityq=0 表示"不要这种编码",且编码按完整 token 匹配
xdeflateidentity非标准名称不匹配
gzip, deflate;q=0.5content-encoding: deflate多编码列表按 q 值协商
br(仅LWS_WITH_HTTP_BROTLI构建时)content-encoding: br只在编译期启用 brotli 时加入用例
gzip(h2)identity同上,h2 路径重复验证

注意表格中的关键事实:lws 对普通文件不主动提供 gzip 编码。原因可从服务端源码验证:lws 的可用压缩方法表lcs_available[](stream.c)只包含lcs_brotli与lcs_deflate两项;而 gzip 仅在文件本身以.gz预压缩形式存储、可被直接转发时才会以content-encoding: gzip提供(见 server.c 的LWS_FOP_FLAG_COMPR_IS_GZIP分支)。这与测试矩阵中gzip期望 identity 的行为完全一致。

测试文件生成:sha256 命名与"有真实工作可做"的内容

程序启动时(generate_file())做如下事情:

  1. 在--tmpdir下用mkdtemp创建临时目录;
  2. 用xorshift32伪随机数(种子0x12345678)生成十六进制数字流,并交错插入重复单词(lorem/ipsum/dolor/sit/amet/...等 12 个词,按随机值选取);
  3. 边写文件边用lws_genhash_update累积计算sha256;
  4. 写完将文件重命名为<sha256hex>.txt。

这种内容构造大有讲究:纯随机数据不可压缩,纯重复文本会被压成极小体量,而"伪随机 hex 流 + 重复词交错"既给了压缩器真实的工作量,又不会让文件塌缩,从而能真正检验压缩器在中等压缩比下的正确性。文件是text/plain,因为lws 只压缩"文本类" MIME(见下文)。

服务端与客户端结构:一个进程、三个 vhost

main()(main.c)创建了:

  • 一个主 context,port = CONTEXT_PORT_NO_LISTEN(不监听)、LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
  • srv-h1 vhost:监听-p指定的端口(默认 7681),挂载 file mount,协议仅lws_callback_http_dummy;
  • srv-h2 vhost:监听--h2-port指定端口(默认 7682),设置LWS_SERVER_OPTION_H2_PRIOR_KNOWLEDGE(仅LWS_WITH_HTTP2构建时创建);
  • cli vhost:不监听,注册客户端协议http-comp,负责发起所有抓取请求。

文件挂载mount(main.c)是经典的LWSMPRO_FILE类型,origin指向临时目录,mountpoint = "/"。客户端通过lws_client_connect_via_info逐 case 发起连接:h1 用例走port_h1,h2 用例走port_h2且ssl_connection = LCCSCF_H2_PRIOR_KNOWLEDGE(h2c 明文 prior knowledge)。

压缩协商的底层实现

accept-encoding 的解析与 accept mask

服务端在响应阶段调用lws_http_compression_validate()(stream.c)解析请求头WSI_TOKEN_HTTP_ACCEPT_ENCODING:用lws_tokenize按 RFC 7230 分隔符切分编码列表,逐个 token 与lcs_available[]中的编码名做整 token 不区分大小写匹配,并识别;q=0参数——q 值为 0 时将该编码标记为"拒绝"。结果写入wsi->http.comp_accept_mask位掩码,后续lws_http_compression_apply()只有在掩码对应位被置位(或本就是服务端主动解码)时才允许选用该压缩方法。这解释了测试矩阵中deflate;q=0期望 identity、xdeflate期望 identity 的行为。

压缩方法注册表:deflate 与 brotli

所有压缩方法通过统一的struct lws_compression_support接口(private-lib-roles-http-compression.h)暴露encoding_name、init_compression、process、destroy四个成员:

  • deflate(deflate.c):实现采用zlib 格式(RFC 9110 8.4.1.2 / RFC 1950),即windowBits 15,而非 raw deflate(-15)——源码注释明确指出 raw deflate 只有部分客户端能处理。压缩参数为deflateInit2(level=1, Z_DEFLATED, 15, memLevel=8, Z_DEFAULT_STRATEGY),级别取 1 优先保证吞吐;用Z_SYNC_FLUSH维持流式输出,最终帧用Z_FINISH收尾。测试客户端一侧则用inflateInit2(&zs, 15)与之一致地解码;
  • brotli(brotli.c):BROTLI_PARAM_MODE设为BROTLI_MODE_TEXT(文本优化模式),BROTLI_PARAM_QUALITY设为BROTLI_MIN_QUALITY(最小质量档);编码时区分BROTLI_OPERATION_PROCESS与BROTLI_OPERATION_FINISH,解码时对BROTLI_DECODER_RESULT_ERROR记录错误码与错误字符串。

变换管线:buflist_comp 与帧边界管理

压缩变换与 h1/h2 传输层解耦(compression/README.md):它新增一类特殊 buflistwsi->buflist_comp,暂存"已交付但压缩器暂未消化"的输入。lws_http_compression_transform()(stream.c)的核心逻辑包括:

  • 只在LWS_WRITE_HTTP/LWS_WRITE_HTTP_FINAL写类型上做变换;
  • 收到 FINAL 输入后先降级为非 FINAL,等压缩器消化完缓冲区再用一次无输入调用触发真正收尾,并把最终输出帧升级回LWS_WRITE_HTTP_FINAL;
  • 输入被压缩器部分消费时,剩余部分追加到buflist_comp尾部,并触发lws_callback_on_writable继续处理;
  • 压缩器表示可能还有更多输出(may_have_more)时同样回调可写,确保多帧输出不被截断。

什么内容才被压缩:文本 MIME 与 206 例外

服务端发响应时(server.c)只有满足全部条件才调用lws_http_compression_apply():

  • 不是 CGI 解释输出(!wsi->interpreting);
  • 不是 206 Partial Content(开启LWS_WITH_RANGES时!ranges,因为 Content-Range/multipart part 头描述的是字节偏移,套上编码会使其变成"编码后字节"的偏移,违反 RFC 9110 14.4);
  • content_type以text/开头,或等于application/javascript、image/svg+xml。

这正是文档所述"lws 只压缩文本类 MIME"的代码级依据——测试文件刻意使用text/plain就是为了命中该分支。压缩应用成功后,content-encoding头与响应头一起被写入(若头部空间不够则放弃压缩,主体按原样发送,保证协议自洽)。

客户端解码与校验:解码、计数、摘要三管齐下

每个测试用例由struct conn(main.c)承载状态,包含lws_genhash_ctx摘要上下文、z_stream或BrotliDecoderState、收发字节计数、content-encoding记录等。客户端回调callback_cli的关键路径:

  • LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:用lws_add_http_header_by_token附加WSI_TOKEN_HTTP_ACCEPT_ENCODING(若该用例配置了 accept 值);
  • LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:记录状态码与响应content-encoding;
  • LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:把收到的每个 body 分片送入conn_rx()解码;
  • LWS_CALLBACK_COMPLETED_CLIENT_HTTP/LWS_CALLBACK_CLOSED_CLIENT_HTTP:收尾判定。

conn_rx()(main.c)按解码器类型分流:identity 直接进摘要;deflate 用inflate()(接受Z_OK/Z_STREAM_END/Z_BUF_ERROR,出现Z_STREAM_END置stream_ended);brotli 用BrotliDecoderDecompressStream循环驱动直到BROTLI_DECODER_RESULT_SUCCESS。所有解码输出都同步喂给 sha256 摘要。

conn_finish()(main.c)做最终判定,任何一项失败都计为 failure:

  1. 连接是否在完整结束前关闭;
  2. HTTP 状态是否 200;
  3. content-encoding是否与用例预期一致(预期有则必须出现且相等,预期无则必须不出现);
  4. 压缩流是否干净结束(stream_ended);
  5. 解码后字节数是否等于file_size;
  6. 解码流的 sha256 是否与文件名中的摘要一致。

每个用例结束打印PASS/FAIL及"线上字节数 vs 解码字节数"(例如h1 accept deflate: PASS (x bytes on the wire, y decoded)),通过对比可直观看到压缩节省的带宽。

构建要求与依赖

原文档的构建前提(README.md)与 CMakeLists.txt 完全对应:

  • LWS_WITH_HTTP_STREAM_COMPRESSION:必选,开启服务端流式压缩(顶层 CMakeLists.txt 默认 OFF,开启后强制启用 zlib 支持);
  • 同时具备 server 与 client 构建:require_lws_config检查LWS_ROLE_H1、LWS_WITH_NETWORK、LWS_WITH_CLIENT且不允许LWS_WITHOUT_SERVER;
  • LWS_WITH_GENCRYPTO:必选,用于lws_genhash_*计算 sha256 摘要;
  • 链接zlib(find_package(ZLIB REQUIRED));当 lws 以LWS_WITH_HTTP_BROTLI构建时额外链接brotlidec(brotli/decode.h 同库族)。

文档注释点明了链接 zlib/brotlidec 的原因:客户端以"浏览器同款方式"解码服务端压缩流,因此测试程序直接复用了 lws 压缩时所用的同一批底层解码库。

构建与运行(在已构建好上述特性的 lws 环境下):

$ cmake . && make $ ./lws-api-test-http-compression -p 7681 --h2-port 7682 [--size bytes] [--tmpdir dir] [-d 1039]

命令行参数与退出码

参数默认值说明
-p7681h1 服务端口
--h2-port7682h2c 服务端口(仅LWS_WITH_HTTP2时启用)
--size3 MiB(3 × 1024 × 1024 字节)生成文件大小,可用atol指定字节数
--tmpdir当前目录临时文件目录;ctest 会传入其构建目录
-d1039日志级别(LLL_USER|LLL_ERR|LLL_WARN|LLL_NOTICE)

生成文件位于--tmpdir下新建的临时子目录,进程退出时被清理(unlink文件 +rmdir目录)。退出码只有 0 表示全部用例通过,任一用例失败则返回 1;另有 90 秒看门狗定时器,超时也会判失败。此外还安装了 SIGINT 处理器支持手动中断。

ctest 集成:两个互补用例

CMakeLists.txt 注册了两个测试:

  • api-test-http-compression:默认 3 MiB 文件,覆盖全部协商矩阵;lws_get_free_port动态分配 h1/h2 端口,TIMEOUT 120;
  • api-test-http-compression-empty:--size 0生成空文件——此时压缩器没有实质输入,但"流结束"仍必须正常终结 body(h1 上体现为最后一个 chunk)。该用例专门验证空 body 场景下压缩管线的收尾正确性。

两者共享WORKING_DIRECTORY为源码目录、--tmpdir指向构建目录,保证产物位置一致。

小结

lws-api-test-http-compression通过"sha256 命名文件 + 浏览器同款解码 + 摘要/长度双校验"的闭环设计,把 HTTP 流式压缩的正确性验证做到了可自动判定、零误报的程度。结合 stream.c、deflate.c、brotli.c 等实现源码,可以完整看到 lws 在编码协商(accept mask)、文本 MIME 过滤、206 例外、buflist 缓冲与帧收尾上的工程细节。无论你是要为自己的 lws 服务端启用LWS_WITH_HTTP_STREAM_COMPRESSION,还是要排查压缩传输的正确性,这个自测程序都是最直接的参考与验证工具。

  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

相关推荐

上一篇:5分钟上手!AWS CLI备份恢复实战:从计划到灾难恢复全流程
下一篇:Dokploy项目中Traefik证书解析器的自定义配置指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询