news 2026/10/14 15:45:17

libwebsockets 自定义 HTTP 头实战:minimal-http-client-custom-headers 示例全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libwebsockets 自定义 HTTP 头实战:minimal-http-client-custom-headers 示例全解析
  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

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

本篇围绕 libwebsockets 仓库中的 minimal-http-client-custom-headers 最小示例展开,讲解 HTTP 客户端如何发送自定义请求头(如dnt: 1)以及如何在收到响应后查询服务器下发的自定义头(如test-custom-header)。读完本文,你将掌握lws_add_http_header_by_name()、lws_hdr_custom_length()/lws_hdr_custom_copy()这组自定义头 API 的完整用法、对应的回调时机,以及该能力仅支持 HTTP/1 的原因和源码层面的实现原理。

一、示例定位与能力边界

该示例位于 minimal-examples-lowlevel/http-client/minimal-http-client-custom-headers/,其 README 明确说明两件事:

  • 应用演示如何发送和接收自定义头;
  • 目前只在 HTTP/1 上工作,因此即使构建时启用了 h2,应用也会强制使用 HTTP/1.1(源码中通过i.alpn = "http/1.1"实现,见下文)。

从 顶层 README 的示例列表 也可以确认其定位:"Shows how to send and receive custom headers (h1 only)"。

二、构建与命令行用法(完整继承原文档)

构建方式(README 原文):

$ cmake . && make

该示例的 CMakeLists.txt 通过require_lws_config检查三项依赖,未满足则直接不构建目标:

require_lws_config(LWS_ROLE_H1 1 requirements) require_lws_config(LWS_WITH_CLIENT 1 requirements) require_lws_config(LWS_WITH_CUSTOM_HEADERS 1 requirements)

其中LWS_WITH_CUSTOM_HEADERS是启用自定义头存储与查询的编译开关,在 顶层 CMakeLists.txt 中定义为 ON(默认开启),其注释为 "Store and allow querying custom HTTP headers (H1 only)"——从这一措辞可直接印证 README 中 "H1 only" 的限制。

命令行参数(README 参数表):

Commandline optionMeaning
-dDebug verbosity in decimal, eg, -d15
-lConnect to https://localhost:7681 and accept selfsigned cert
-nno TLS

对应源码中的struct lws_switches定义(minimal-http-client-custom-headers.c):

static const struct lws_switches switches[] = { [LWS_SW_D] = { "-d", "Debug logs (e.g. -d 15)" }, [LWS_SW_L] = { "-l", "Enable -l feature" }, [LWS_SW_N] = { "-n", "Enable -n feature" }, [LWS_SW_HELP] = { "--help", "Show this help information" }, };

默认情况下(不带-l),客户端连接warmcat.com:443;带-l时改连localhost:7681并加LCCSCF_ALLOW_SELFSIGNED接受自签证书;带-n时清除LCCSCF_USE_SSL走明文 http。相关分支逻辑在 main() 中:

if (!lws_cmdline_option(argc, argv, switches[LWS_SW_N].sw)) i.ssl_connection = LCCSCF_USE_SSL; if (lws_cmdline_option(argc, argv, switches[LWS_SW_L].sw)) { i.port = 7681; i.address = "localhost"; i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; } else { i.port = 443; i.address = "warmcat.com"; }

三、运行效果:README 中的两条验证路径

路径 1:访问 warmcat.com 读取服务器自定义头

$ ./lws-minimal-http-client-custom-headers [2019-03-11 05:46:45:7582] USER: LWS minimal http client Custom Headers [-d<verbosity>] [-l] [--h1] [2019-03-11 05:46:45:7671] NOTICE: created client ssl context for default [2019-03-11 05:46:46:7812] USER: Connected with server response: 200 [2019-03-11 05:46:46:7812] NOTICE: callback_http: custom header: 'hello' [2019-03-11 05:46:46:7814] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 ...

应用查找的是服务器返回的test-custom-header(warmcat.com 固定下发值hello)。

路径 2:本地回环验证"请求侧"自定义头

README 指出:使用-n -l让应用以明文 http 连接 localhost:7681,可以用 tcpdump 确认dnt:1头确实发出;或者在本机 :7681 端口运行 test server 并加-d1151日志级别,观察解析器逐字节解析该头的过程:

[2019-03-11 05:48:53:6806] PARSER: WSI_TOKEN_NAME_PART 'd' 0x64 (role=0x20000000) wsi->lextable_pos=0 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART 'n' 0x6E (role=0x20000000) wsi->lextable_pos=567 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART 't' 0x74 (role=0x20000000) wsi->lextable_pos=-1 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART ' ' 0x20 (role=0x20000000) wsi->lextable_pos=-1 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART '1' 0x31 (role=0x20000000) wsi->lextable_pos=-1

这两条路径分别验证了"响应侧读自定义头"与"请求侧发自定义头"两个方向,与标题中的 send/receive 完全对应。

四、发送自定义头:LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER

发送自定义头的标准做法是在客户端握手请求头尚未写出的回调LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER中追加。示例代码(minimal-http-client-custom-headers.c):

case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: { unsigned char **p = (unsigned char **)in, *end = (*p) + len; /* * How to send a custom header in the request to the server */ if (lws_add_http_header_by_name(wsi, (const unsigned char *)"dnt", (const unsigned char *)"1", 1, p, end)) return -1; break; }

要点:

  • in是unsigned char **类型的写指针,end = (*p) + len为缓冲区末尾;lws_add_http_header_by_name()成功后会把*p前移,失败返回非 0,此时应返回 -1 终止连接;
  • 头名可以不带冒号(如"dnt"),libwebsockets 会按已知头/未知头自动处理分隔与拼装,最终线上格式即dnt: 1(路径 2 的 PARSER 日志逐字符印证了这一点);
  • 该 API 声明于 lws-http.h,实现位于 lib/sansio/http/header.c,它把name: value\r\n追加进 wsi 的附加头区(ah),供后续写 socket 时统一发出。

五、接收自定义头:LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP 中查询

收到响应头后(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP),用两个查询 API 读取未知头:

case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: status = (int)lws_http_client_http_response(wsi); lwsl_user("Connected with server response: %d\n", status); /* * How to query custom headers (http 1.x only at the momemnt) * * warmcat.com sends a custom header "test-custom-header" for * testing, it has the fixed value "hello". */ n = lws_hdr_custom_length(wsi, "test-custom-header:", 19); if (n < 0) lwsl_notice("%s: Can't find test-custom-header\n", __func__); else { if (lws_hdr_custom_copy(wsi, val, sizeof(val), "test-custom-header:", 19) < 0) lwsl_notice("%s: custom header too long\n", __func__); else lwsl_notice("%s: custom header: '%s'\n", __func__, val); } break;

这两个 API 的文档(lws-http.h)说明了核心语义:

  • libwebsockets 认识约 100 个常见 HTTP 头,遇到已知头会解析成 token 索引(可用lws_hdr_copy()按WSI_TOKEN_*枚举查询);遇到不认识的自定义头,则把名字和值原样存起来,用lws_hdr_custom_length()/lws_hdr_custom_copy()查询;
  • name参数要求带结尾冒号("test-custom-header:",长度 19)。源码实现 lws_hdr_custom_length() 和 lws_hdr_custom_copy() 通过strncmp与存储的名字做区分大小写的精确匹配,这就是为什么必须带上:且大小写要与服务器下发一致(另有一个lws_hdr_custom_name_foreach()回调式遍历 API 可枚举全部自定义头名,见 lws-http.h);
  • lws_hdr_custom_length()返回 -1(不存在)或值部分长度;lws_hdr_custom_copy()要求缓冲区能容纳值外加一个结尾 NUL,否则返回 -1;
  • 使用这些 API 的前提是库以LWS_WITH_CUSTOM_HEADERS编译(默认开启),源码中所有实现都被#if defined(LWS_WITH_CUSTOM_HEADERS)包裹,见 parsers.c。

六、从源码看:未知头是如何被存储的

"为什么只有 H1" 可以从 H1 解析器对未知头的存储机制中理解。lib/sansio/http/parsers.c 中定义了未知头(UHO)在 ah 缓冲区内的条目布局,偏移量定义在 private-lib-roles-http.h:

#define UHO_NLEN 0 #define UHO_VLEN 2 #define UHO_LL 4 #define UHO_NAME 8

即每个未知头是一条变长记录:name 长度(2B) | value 长度(2B) | 下一条指针(4B) | 名字+值。当 H1 解析器发现某个头名不在已知表中时,lws_h1_unknown_name_ended()会把当前ah->unk_pos登记进unk_ll_head开头的单向链表(UHO_LL存 next 指针),随后进入WSI_TOKEN_UNKNOWN_VALUE_PART状态收集值直到下一个 CRLF。上述lws_hdr_custom_length()等查询 API 正是沿这条链表做逐节点匹配——这解释了 README "http 1.x only" 的限制来源:这条"未知头链表"机制是 H1 解析路径(WSI_TOKEN_*状态机)特有的;H2/H3 的头在 hpack/HPACK 解码后走的是另一套 header 表示,查询路径与数据布局不同(hpack 侧虽有LWS_WITH_CUSTOM_HEADERS分支,但该 minimal 示例通过固定i.alpn = "http/1.1"主动规避了 h2 场景,见 main() 注释)。

七、响应体的读取与回调闭环

示例还展示了 H1 客户端读取响应体的标准两段式:

/* chunks of chunked content, with header removed */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); return 0; /* don't passthru */ /* uninterpreted http content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { char buffer[1024 + LWS_PRE]; char *px = buffer + LWS_PRE; int lenx = sizeof(buffer) - LWS_PRE; if (lws_http_client_read(wsi, &px, &lenx) < 0) return -1; } return 0; /* don't passthru */
  • LWS_CALLBACK_RECEIVE_CLIENT_HTTP拿到原始内容缓冲,需手动调lws_http_client_read()推进;
  • 库剥掉分块/头部后,通过LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ逐块回调干净的负载(这就是运行日志中read 1024的来源);
  • LWS_CALLBACK_COMPLETED_CLIENT_HTTP/LWS_CALLBACK_CLOSED_CLIENT_HTTP中标记client_wsi = NULL、按status != 200置bad,并lws_cancel_service()唤醒主循环退出。

主循环是最小化的:

lws_client_connect_via_info(&i); while (n >= 0 && client_wsi) n = lws_service(context, 0);

另外值得注意的两个细节:info.port = CONTEXT_PORT_NO_LISTEN表明纯客户端不监听任何端口;info.fd_limit_per_thread = LWS_FD_LIMIT_PER_THREAD_MIN + 1告知 libwebsockets 该 context 同时只有一路连接,从而缩小 fd 表分配;在 mbedTLS/wolfSSL 下还需显式info.client_ssl_ca_filepath = "./warmcat.com.cer"(该证书文件随示例提供),而 OpenSSL 直接使用系统信任库。

八、小结

场景关键 API / 回调所在文件
请求侧发自定义头lws_add_http_header_by_name()@LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADERheader.c
响应侧查询自定义头长度lws_hdr_custom_length(wsi, "name:", nlen)parsers.c
响应侧拷贝自定义头值lws_hdr_custom_copy()parsers.c
枚举全部自定义头名lws_hdr_custom_name_foreach()parsers.c
编译开关LWS_WITH_CUSTOM_HEADERS(默认 ON,H1 only)CMakeLists.txt

需要记住的实操要点:查询时头名必须带冒号且大小写一致;自定义头查询当前仅 HTTP/1 路径可用,如示例所示可用i.alpn = "http/1.1"强制降级到 H1;验证请求侧是否发出自定义头,可参照 README 用 tcpdump 或高日志级别(-d1151)的 test server 观察WSI_TOKEN_NAME_PART解析日志。

  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

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

相关推荐

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 15:45:00

Paperxie:AI驱动的学术论文一站式写作工具评测

1. 论文写作工具的革命性突破写论文的朋友们应该都经历过这样的痛苦&#xff1a;好不容易憋出初稿&#xff0c;却发现格式乱七八糟&#xff1b;画个示意图要折腾半天Visio&#xff1b;查重报告出来的时候心跳直接飙到180。现在有个叫Paperxie的工具号称能一站式解决这些问题&am…

作者头像 李华
网站建设 2026/10/14 15:44:45

数据库原理核心知识框架:从数据模型到SQL优化实战

数据库原理这门课&#xff0c;无论是计算机专业的必修考试&#xff0c;还是技术面试的算法与基础环节&#xff0c;都是绕不开的一道坎。市面上讲数据库的书很多&#xff0c;但大多数知识点分散在教材的不同章节里&#xff0c;初学者很容易陷入“学完索引忘了事务&#xff0c;搞…

作者头像 李华
网站建设 2026/10/14 15:44:24

Apache Iceberg FileIO 深度解析:面向多存储的读写抽象与实现机制

数据湖湖仓一体大数据 【免费下载链接】iceberg Apache Iceberg 项目地址&#xff1a; https://gitcode.com/gh_mirrors/iceberg5/iceberg 点击查看 免费下载 Iceberg 的 FileIO 是连接 Iceberg 库与底层存储层的可插拔抽象&#xff0c;负责元数据与数据文件在作业规划、提交阶…

作者头像 李华
网站建设 2026/10/14 15:44:11

SWE-MasterWorld:虚拟化开发环境框架的技术解析与实践

1. 项目背景与核心价值SWE-Master&World是中国人民大学高瓴人工智能学院近期发布的一项重要研究成果&#xff0c;它从根本上解决了软件工程&#xff08;SWE&#xff09;领域长期存在的环境限制问题。在传统软件开发过程中&#xff0c;工程师们经常面临开发环境配置复杂、依…

作者头像 李华
网站建设 2026/10/14 15:43:30

PL/SQL Developer连接Oracle失败的6大根源与排查体系

1. 为什么PL/SQL Developer配Oracle总像在解谜——一个老手的十年踩坑实录刚入行那会儿&#xff0c;我第一次在本地装PL/SQL Developer连远程Oracle库&#xff0c;整整三天没连上。不是报ORA-12154“TNS无法解析服务名”&#xff0c;就是ORA-12541“无监听器”&#xff0c;再不…

作者头像 李华